diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-03-29 14:32:26 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-29 14:32:26 +0800 |
commit | f4538791f5fc82b173608fcf9c30e36ec01dc9d3 (patch) | |
tree | eaeacc558e9b773eae9268a99146f351dd21a980 /cmd | |
parent | 79e7a6ec1e14c820c8475173a37aee84f2f4f1e8 (diff) | |
download | gitea-f4538791f5fc82b173608fcf9c30e36ec01dc9d3.tar.gz gitea-f4538791f5fc82b173608fcf9c30e36ec01dc9d3.zip |
Refactor internal API for git commands, use meaningful messages instead of "Internal Server Error" (#23687)
# Why this PR comes
At first, I'd like to help users like #23636 (there are a lot)
The unclear "Internal Server Error" is quite anonying, scare users,
frustrate contributors, nobody knows what happens.
So, it's always good to provide meaningful messages to end users (of
course, do not leak sensitive information).
When I started working on the "response message to end users", I found
that the related code has a lot of technical debt. A lot of copy&paste
code, unclear fields and usages.
So I think it's good to make everything clear.
# Tech Backgrounds
Gitea has many sub-commands, some are used by admins, some are used by
SSH servers or Git Hooks. Many sub-commands use "internal API" to
communicate with Gitea web server.
Before, Gitea server always use `StatusCode + Json "err" field` to
return messages.
* The CLI sub-commands: they expect to show all error related messages
to site admin
* The Serv/Hook sub-commands (for git clients): they could only show
safe messages to end users, the error log could only be recorded by
"SSHLog" to Gitea web server.
In the old design, it assumes that:
* If the StatusCode is 500 (in some functions), then the "err" field is
error log, shouldn't be exposed to git client.
* If the StatusCode is 40x, then the "err" field could be exposed. And
some functions always read the "err" no matter what the StatusCode is.
The old code is not strict, and it's difficult to distinguish the
messages clearly and then output them correctly.
# This PR
To help to remove duplicate code and make everything clear, this PR
introduces `ResponseExtra` and `requestJSONResp`.
* `ResponseExtra` is a struct which contains "extra" information of a
internal API response, including StatusCode, UserMsg, Error
* `requestJSONResp` is a generic function which can be used for all
cases to help to simplify the calls.
* Remove all `map["err"]`, always use `private.Response{Err}` to
construct error messages.
* User messages and error messages are separated clearly, the `fail` and
`handleCliResponseExtra` will output correct messages.
* Replace all `Internal Server Error` messages with meaningful (still
safe) messages.
This PR saves more than 300 lines, while makes the git client messages
more clear.
Many gitea-serv/git-hook related essential functions are covered by
tests.
---------
Co-authored-by: delvh <dev.lh@web.de>
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/hook.go | 144 | ||||
-rw-r--r-- | cmd/hook_test.go | 10 | ||||
-rw-r--r-- | cmd/keys.go | 9 | ||||
-rw-r--r-- | cmd/mailer.go | 12 | ||||
-rw-r--r-- | cmd/manager.go | 49 | ||||
-rw-r--r-- | cmd/manager_logging.go | 93 | ||||
-rw-r--r-- | cmd/restore_repo.go | 12 | ||||
-rw-r--r-- | cmd/serv.go | 97 |
8 files changed, 184 insertions, 242 deletions
diff --git a/cmd/hook.go b/cmd/hook.go index a62ffdde5f..9605fcb331 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -6,9 +6,9 @@ package cmd import ( "bufio" "bytes" + "context" "fmt" "io" - "net/http" "os" "strconv" "strings" @@ -167,11 +167,11 @@ func runHookPreReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("hooks/pre-receive.log", c.Bool("debug")) + setup(ctx, c.Bool("debug")) if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if setting.OnlyAllowPushIfGiteaEnvironmentSet { - return fail(`Rejecting changes as Gitea environment not set. + return fail(ctx, `Rejecting changes as Gitea environment not set. If you are pushing over SSH you must push with a key managed by Gitea or set your environment appropriately.`, "") } @@ -257,14 +257,9 @@ Gitea or set your environment appropriately.`, "") hookOptions.OldCommitIDs = oldCommitIDs hookOptions.NewCommitIDs = newCommitIDs hookOptions.RefFullNames = refFullNames - statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions) - switch statusCode { - case http.StatusOK: - // no-op - case http.StatusInternalServerError: - return fail("Internal Server Error", msg) - default: - return fail(msg, "") + extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) } count = 0 lastline = 0 @@ -285,12 +280,9 @@ Gitea or set your environment appropriately.`, "") fmt.Fprintf(out, " Checking %d references\n", count) - statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions) - switch statusCode { - case http.StatusInternalServerError: - return fail("Internal Server Error", msg) - case http.StatusForbidden: - return fail(msg, "") + extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error) } } else if lastline > 0 { fmt.Fprintf(out, "\n") @@ -309,7 +301,7 @@ func runHookPostReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("hooks/post-receive.log", c.Bool("debug")) + setup(ctx, c.Bool("debug")) // First of all run update-server-info no matter what if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { @@ -323,7 +315,7 @@ func runHookPostReceive(c *cli.Context) error { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if setting.OnlyAllowPushIfGiteaEnvironmentSet { - return fail(`Rejecting changes as Gitea environment not set. + return fail(ctx, `Rejecting changes as Gitea environment not set. If you are pushing over SSH you must push with a key managed by Gitea or set your environment appropriately.`, "") } @@ -394,11 +386,11 @@ Gitea or set your environment appropriately.`, "") hookOptions.OldCommitIDs = oldCommitIDs hookOptions.NewCommitIDs = newCommitIDs hookOptions.RefFullNames = refFullNames - resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) - if resp == nil { + resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) + if extra.HasError() { _ = dWriter.Close() hookPrintResults(results) - return fail("Internal Server Error", err) + return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error) } wasEmpty = wasEmpty || resp.RepoWasEmpty results = append(results, resp.Results...) @@ -409,9 +401,9 @@ Gitea or set your environment appropriately.`, "") if count == 0 { if wasEmpty && masterPushed { // We need to tell the repo to reset the default branch to master - err := private.SetDefaultBranch(ctx, repoUser, repoName, "master") - if err != nil { - return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) + extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master") + if extra.HasError() { + return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error) } } fmt.Fprintf(out, "Processed %d references in total\n", total) @@ -427,11 +419,11 @@ Gitea or set your environment appropriately.`, "") fmt.Fprintf(out, " Processing %d references\n", count) - resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) + resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) if resp == nil { _ = dWriter.Close() hookPrintResults(results) - return fail("Internal Server Error", err) + return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error) } wasEmpty = wasEmpty || resp.RepoWasEmpty results = append(results, resp.Results...) @@ -440,9 +432,9 @@ Gitea or set your environment appropriately.`, "") if wasEmpty && masterPushed { // We need to tell the repo to reset the default branch to master - err := private.SetDefaultBranch(ctx, repoUser, repoName, "master") - if err != nil { - return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) + extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master") + if extra.HasError() { + return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error) } } _ = dWriter.Close() @@ -485,22 +477,22 @@ func pushOptions() map[string]string { } func runHookProcReceive(c *cli.Context) error { - setup("hooks/proc-receive.log", c.Bool("debug")) + ctx, cancel := installSignals() + defer cancel() + + setup(ctx, c.Bool("debug")) if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if setting.OnlyAllowPushIfGiteaEnvironmentSet { - return fail(`Rejecting changes as Gitea environment not set. + return fail(ctx, `Rejecting changes as Gitea environment not set. If you are pushing over SSH you must push with a key managed by Gitea or set your environment appropriately.`, "") } return nil } - ctx, cancel := installSignals() - defer cancel() - if git.CheckGitVersionAtLeast("2.29") != nil { - return fail("Internal Server Error", "git not support proc-receive.") + return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.") } reader := bufio.NewReader(os.Stdin) @@ -515,7 +507,7 @@ Gitea or set your environment appropriately.`, "") // H: PKT-LINE(version=1\0push-options...) // H: flush-pkt - rs, err := readPktLine(reader, pktLineTypeData) + rs, err := readPktLine(ctx, reader, pktLineTypeData) if err != nil { return err } @@ -530,19 +522,19 @@ Gitea or set your environment appropriately.`, "") index := bytes.IndexByte(rs.Data, byte(0)) if index >= len(rs.Data) { - return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data)) + return fail(ctx, "Protocol: format error", "pkt-line: format error "+fmt.Sprint(rs.Data)) } if index < 0 { if len(rs.Data) == 10 && rs.Data[9] == '\n' { index = 9 } else { - return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data)) + return fail(ctx, "Protocol: format error", "pkt-line: format error "+fmt.Sprint(rs.Data)) } } if string(rs.Data[0:index]) != VersionHead { - return fail("Internal Server Error", "Received unsupported version: %s", string(rs.Data[0:index])) + return fail(ctx, "Protocol: version error", "Received unsupported version: %s", string(rs.Data[0:index])) } requestOptions = strings.Split(string(rs.Data[index+1:]), " ") @@ -555,17 +547,17 @@ Gitea or set your environment appropriately.`, "") } response = append(response, '\n') - _, err = readPktLine(reader, pktLineTypeFlush) + _, err = readPktLine(ctx, reader, pktLineTypeFlush) if err != nil { return err } - err = writeDataPktLine(os.Stdout, response) + err = writeDataPktLine(ctx, os.Stdout, response) if err != nil { return err } - err = writeFlushPktLine(os.Stdout) + err = writeFlushPktLine(ctx, os.Stdout) if err != nil { return err } @@ -588,7 +580,7 @@ Gitea or set your environment appropriately.`, "") for { // note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed - rs, err = readPktLine(reader, pktLineTypeUnknow) + rs, err = readPktLine(ctx, reader, pktLineTypeUnknow) if err != nil { return err } @@ -609,7 +601,7 @@ Gitea or set your environment appropriately.`, "") if hasPushOptions { for { - rs, err = readPktLine(reader, pktLineTypeUnknow) + rs, err = readPktLine(ctx, reader, pktLineTypeUnknow) if err != nil { return err } @@ -626,9 +618,9 @@ Gitea or set your environment appropriately.`, "") } // 3. run hook - resp, err := private.HookProcReceive(ctx, repoUser, repoName, hookOptions) - if err != nil { - return fail("Internal Server Error", "run proc-receive hook failed :%v", err) + resp, extra := private.HookProcReceive(ctx, repoUser, repoName, hookOptions) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "HookProcReceive failed: %v", extra.Error) } // 4. response result to service @@ -649,7 +641,7 @@ Gitea or set your environment appropriately.`, "") for _, rs := range resp.Results { if len(rs.Err) > 0 { - err = writeDataPktLine(os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err)) + err = writeDataPktLine(ctx, os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err)) if err != nil { return err } @@ -657,43 +649,43 @@ Gitea or set your environment appropriately.`, "") } if rs.IsNotMatched { - err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef)) + err = writeDataPktLine(ctx, os.Stdout, []byte("ok "+rs.OriginalRef)) if err != nil { return err } - err = writeDataPktLine(os.Stdout, []byte("option fall-through")) + err = writeDataPktLine(ctx, os.Stdout, []byte("option fall-through")) if err != nil { return err } continue } - err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef)) + err = writeDataPktLine(ctx, os.Stdout, []byte("ok "+rs.OriginalRef)) if err != nil { return err } - err = writeDataPktLine(os.Stdout, []byte("option refname "+rs.Ref)) + err = writeDataPktLine(ctx, os.Stdout, []byte("option refname "+rs.Ref)) if err != nil { return err } if rs.OldOID != git.EmptySHA { - err = writeDataPktLine(os.Stdout, []byte("option old-oid "+rs.OldOID)) + err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID)) if err != nil { return err } } - err = writeDataPktLine(os.Stdout, []byte("option new-oid "+rs.NewOID)) + err = writeDataPktLine(ctx, os.Stdout, []byte("option new-oid "+rs.NewOID)) if err != nil { return err } if rs.IsForcePush { - err = writeDataPktLine(os.Stdout, []byte("option forced-update")) + err = writeDataPktLine(ctx, os.Stdout, []byte("option forced-update")) if err != nil { return err } } } - err = writeFlushPktLine(os.Stdout) + err = writeFlushPktLine(ctx, os.Stdout) return err } @@ -718,7 +710,7 @@ type gitPktLine struct { Data []byte } -func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) { +func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) { var ( err error r *gitPktLine @@ -729,33 +721,33 @@ func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) for i := 0; i < 4; i++ { lengthBytes[i], err = in.ReadByte() if err != nil { - return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err) + return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err) } } r = new(gitPktLine) r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32) if err != nil { - return nil, fail("Internal Server Error", "Pkt-Line format is wrong :%v", err) + return nil, fail(ctx, "Protocol: format parse error", "Pkt-Line format is wrong :%v", err) } if r.Length == 0 { if requestType == pktLineTypeData { - return nil, fail("Internal Server Error", "Pkt-Line format is wrong") + return nil, fail(ctx, "Protocol: format data error", "Pkt-Line format is wrong") } r.Type = pktLineTypeFlush return r, nil } if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush { - return nil, fail("Internal Server Error", "Pkt-Line format is wrong") + return nil, fail(ctx, "Protocol: format length error", "Pkt-Line format is wrong") } r.Data = make([]byte, r.Length-4) for i := range r.Data { r.Data[i], err = in.ReadByte() if err != nil { - return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err) + return nil, fail(ctx, "Protocol: data error", "Pkt-Line: read stdin failed : %v", err) } } @@ -764,19 +756,15 @@ func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) return r, nil } -func writeFlushPktLine(out io.Writer) error { +func writeFlushPktLine(ctx context.Context, out io.Writer) error { l, err := out.Write([]byte("0000")) - if err != nil { - return fail("Internal Server Error", "Pkt-Line response failed: %v", err) + if err != nil || l != 4 { + return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err) } - if l != 4 { - return fail("Internal Server Error", "Pkt-Line response failed: %v", err) - } - return nil } -func writeDataPktLine(out io.Writer, data []byte) error { +func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error { hexchar := []byte("0123456789abcdef") hex := func(n uint64) byte { return hexchar[(n)&15] @@ -790,19 +778,13 @@ func writeDataPktLine(out io.Writer, data []byte) error { tmp[3] = hex(length) lr, err := out.Write(tmp) - if err != nil { - return fail("Internal Server Error", "Pkt-Line response failed: %v", err) - } - if lr != 4 { - return fail("Internal Server Error", "Pkt-Line response failed: %v", err) + if err != nil || lr != 4 { + return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err) } lr, err = out.Write(data) - if err != nil { - return fail("Internal Server Error", "Pkt-Line response failed: %v", err) - } - if int(length-4) != lr { - return fail("Internal Server Error", "Pkt-Line response failed: %v", err) + if err != nil || int(length-4) != lr { + return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err) } return nil diff --git a/cmd/hook_test.go b/cmd/hook_test.go index fe1f072a6f..91f24ff2b4 100644 --- a/cmd/hook_test.go +++ b/cmd/hook_test.go @@ -6,6 +6,7 @@ package cmd import ( "bufio" "bytes" + "context" "strings" "testing" @@ -14,27 +15,28 @@ import ( func TestPktLine(t *testing.T) { // test read + ctx := context.Background() s := strings.NewReader("0000") r := bufio.NewReader(s) - result, err := readPktLine(r, pktLineTypeFlush) + result, err := readPktLine(ctx, r, pktLineTypeFlush) assert.NoError(t, err) assert.Equal(t, pktLineTypeFlush, result.Type) s = strings.NewReader("0006a\n") r = bufio.NewReader(s) - result, err = readPktLine(r, pktLineTypeData) + result, err = readPktLine(ctx, r, pktLineTypeData) assert.NoError(t, err) assert.Equal(t, pktLineTypeData, result.Type) assert.Equal(t, []byte("a\n"), result.Data) // test write w := bytes.NewBuffer([]byte{}) - err = writeFlushPktLine(w) + err = writeFlushPktLine(ctx, w) assert.NoError(t, err) assert.Equal(t, []byte("0000"), w.Bytes()) w.Reset() - err = writeDataPktLine(w, []byte("a\nb")) + err = writeDataPktLine(ctx, w, []byte("a\nb")) assert.NoError(t, err) assert.Equal(t, []byte("0007a\nb"), w.Bytes()) } diff --git a/cmd/keys.go b/cmd/keys.go index 74dc1cc68c..deb94fca5d 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -64,11 +64,12 @@ func runKeys(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("keys.log", false) + setup(ctx, false) - authorizedString, err := private.AuthorizedPublicKeyByContent(ctx, content) - if err != nil { - return err + authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) + // do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys + if extra.Error != nil { + return extra.Error } fmt.Println(strings.TrimSpace(authorizedString)) return nil diff --git a/cmd/mailer.go b/cmd/mailer.go index d05fee12bc..50ba4b4741 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -5,7 +5,6 @@ package cmd import ( "fmt" - "net/http" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" @@ -43,13 +42,10 @@ func runSendMail(c *cli.Context) error { } } - status, message := private.SendEmail(ctx, subject, body, nil) - if status != http.StatusOK { - fmt.Printf("error: %s\n", message) - return nil + respText, extra := private.SendEmail(ctx, subject, body, nil) + if extra.HasError() { + return handleCliResponseExtra(extra) } - - fmt.Printf("Success: %s\n", message) - + _, _ = fmt.Printf("Sent %s email(s) to all users\n", respText) return nil } diff --git a/cmd/manager.go b/cmd/manager.go index cdfe509075..3f1e223190 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -4,8 +4,6 @@ package cmd import ( - "fmt" - "net/http" "os" "time" @@ -103,57 +101,34 @@ func runShutdown(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.Shutdown(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil + setup(ctx, c.Bool("debug")) + extra := private.Shutdown(ctx) + return handleCliResponseExtra(extra) } func runRestart(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.Restart(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil + setup(ctx, c.Bool("debug")) + extra := private.Restart(ctx) + return handleCliResponseExtra(extra) } func runFlushQueues(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil + setup(ctx, c.Bool("debug")) + extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) + return handleCliResponseExtra(extra) } func runProcesses(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - return nil + setup(ctx, c.Bool("debug")) + extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) + return handleCliResponseExtra(extra) } diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index d49675ce87..914210d370 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -5,7 +5,6 @@ package cmd import ( "fmt" - "net/http" "os" "code.gitea.io/gitea/modules/log" @@ -191,27 +190,25 @@ var ( ) func runRemoveLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) + ctx, cancel := installSignals() + defer cancel() + + setup(ctx, c.Bool("debug")) group := c.String("group") if len(group) == 0 { group = log.DEFAULT } name := c.Args().First() - ctx, cancel := installSignals() - defer cancel() - - statusCode, msg := private.RemoveLogger(ctx, group, name) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - fmt.Fprintln(os.Stdout, msg) - return nil + extra := private.RemoveLogger(ctx, group, name) + return handleCliResponseExtra(extra) } func runAddSMTPLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) + ctx, cancel := installSignals() + defer cancel() + + setup(ctx, c.Bool("debug")) vals := map[string]interface{}{} mode := "smtp" if c.IsSet("host") { @@ -242,7 +239,10 @@ func runAddSMTPLogger(c *cli.Context) error { } func runAddConnLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) + ctx, cancel := installSignals() + defer cancel() + + setup(ctx, c.Bool("debug")) vals := map[string]interface{}{} mode := "conn" vals["net"] = "tcp" @@ -269,7 +269,10 @@ func runAddConnLogger(c *cli.Context) error { } func runAddFileLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) + ctx, cancel := installSignals() + defer cancel() + + setup(ctx, c.Bool("debug")) vals := map[string]interface{}{} mode := "file" if c.IsSet("filename") { @@ -299,7 +302,10 @@ func runAddFileLogger(c *cli.Context) error { } func runAddConsoleLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) + ctx, cancel := installSignals() + defer cancel() + + setup(ctx, c.Bool("debug")) vals := map[string]interface{}{} mode := "console" if c.IsSet("stderr") && c.Bool("stderr") { @@ -338,28 +344,17 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) e ctx, cancel := installSignals() defer cancel() - statusCode, msg := private.AddLogger(ctx, group, name, mode, vals) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil + extra := private.AddLogger(ctx, group, name, mode, vals) + return handleCliResponseExtra(extra) } func runPauseLogging(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.PauseLogging(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) + setup(ctx, c.Bool("debug")) + userMsg := private.PauseLogging(ctx) + _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } @@ -367,14 +362,9 @@ func runResumeLogging(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.ResumeLogging(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) + setup(ctx, c.Bool("debug")) + userMsg := private.ResumeLogging(ctx) + _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } @@ -382,28 +372,17 @@ func runReleaseReopenLogging(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - statusCode, msg := private.ReleaseReopenLogging(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) + setup(ctx, c.Bool("debug")) + userMsg := private.ReleaseReopenLogging(ctx) + _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } func runSetLogSQL(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setup("manager", c.Bool("debug")) - - statusCode, msg := private.SetLogSQL(ctx, !c.Bool("off")) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } + setup(ctx, c.Bool("debug")) - fmt.Fprintln(os.Stdout, msg) - return nil + extra := private.SetLogSQL(ctx, !c.Bool("off")) + return handleCliResponseExtra(extra) } diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index c7dff41966..887b59bba9 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -4,11 +4,8 @@ package cmd import ( - "errors" - "net/http" "strings" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" @@ -60,7 +57,7 @@ func runRestoreRepository(c *cli.Context) error { if s := c.String("units"); s != "" { units = strings.Split(s, ",") } - statusCode, errStr := private.RestoreRepo( + extra := private.RestoreRepo( ctx, c.String("repo_dir"), c.String("owner_name"), @@ -68,10 +65,5 @@ func runRestoreRepository(c *cli.Context) error { units, c.Bool("validation"), ) - if statusCode == http.StatusOK { - return nil - } - - log.Fatal("Failed to restore repository: %v", errStr) - return errors.New(errStr) + return handleCliResponseExtra(extra) } diff --git a/cmd/serv.go b/cmd/serv.go index d7510845a5..72eb637071 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -7,7 +7,6 @@ package cmd import ( "context" "fmt" - "net/http" "net/url" "os" "os/exec" @@ -16,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" asymkey_model "code.gitea.io/gitea/models/asymkey" git_model "code.gitea.io/gitea/models/git" @@ -55,7 +55,7 @@ var CmdServ = cli.Command{ }, } -func setup(logPath string, debug bool) { +func setup(ctx context.Context, debug bool) { _ = log.DelLogger("console") if debug { _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`) @@ -72,15 +72,15 @@ func setup(logPath string, debug bool) { // `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection. if _, err := os.Stat(setting.RepoRootPath); err != nil { if os.IsNotExist(err) { - _ = fail("Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath) + _ = fail(ctx, "Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath) } else { - _ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err) + _ = fail(ctx, "Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err) } return } if err := git.InitSimple(context.Background()); err != nil { - _ = fail("Failed to init git", "Failed to init git, err: %v", err) + _ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err) } } @@ -94,24 +94,46 @@ var ( alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) ) -func fail(userMessage, logMessage string, args ...interface{}) error { +// fail prints message to stdout, it's mainly used for git serv and git hook commands. +// The output will be passed to git client and shown to user. +func fail(ctx context.Context, userMessage, logMsgFmt string, args ...interface{}) error { + if userMessage == "" { + userMessage = "Internal Server Error (no specific error)" + } + // There appears to be a chance to cause a zombie process and failure to read the Exit status // if nothing is outputted on stdout. _, _ = fmt.Fprintln(os.Stdout, "") _, _ = fmt.Fprintln(os.Stderr, "Gitea:", userMessage) - if len(logMessage) > 0 { + if logMsgFmt != "" { + logMsg := fmt.Sprintf(logMsgFmt, args...) if !setting.IsProd { - _, _ = fmt.Fprintf(os.Stderr, logMessage+"\n", args...) + _, _ = fmt.Fprintln(os.Stderr, "Gitea:", logMsg) + } + if userMessage != "" { + if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) { + logMsg = userMessage + " " + logMsg + } else { + logMsg = userMessage + ". " + logMsg + } } + _ = private.SSHLog(ctx, true, logMsg) } - ctx, cancel := installSignals() - defer cancel() + return cli.NewExitError("", 1) +} - if len(logMessage) > 0 { - _ = private.SSHLog(ctx, true, fmt.Sprintf(logMessage+": ", args...)) +// handleCliResponseExtra handles the extra response from the cli sub-commands +// If there is a user message it will be printed to stdout +// If the command failed it will return an error (the error will be printed by cli framework) +func handleCliResponseExtra(extra private.ResponseExtra) error { + if extra.UserMsg != "" { + _, _ = fmt.Fprintln(os.Stdout, extra.UserMsg) } - return cli.NewExitError("", 1) + if extra.HasError() { + return cli.NewExitError(extra.Error, 1) + } + return nil } func runServ(c *cli.Context) error { @@ -119,7 +141,7 @@ func runServ(c *cli.Context) error { defer cancel() // FIXME: This needs to internationalised - setup("serv.log", c.Bool("debug")) + setup(ctx, c.Bool("debug")) if setting.SSH.Disabled { println("Gitea: SSH has been disabled") @@ -135,18 +157,18 @@ func runServ(c *cli.Context) error { keys := strings.Split(c.Args()[0], "-") if len(keys) != 2 || keys[0] != "key" { - return fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) + return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args()[0]) } keyID, err := strconv.ParseInt(keys[1], 10, 64) if err != nil { - return fail("Key ID format error", "Invalid key argument: %s", c.Args()[1]) + return fail(ctx, "Key ID parsing error", "Invalid key argument: %s", c.Args()[1]) } cmd := os.Getenv("SSH_ORIGINAL_COMMAND") if len(cmd) == 0 { key, user, err := private.ServNoCommand(ctx, keyID) if err != nil { - return fail("Internal error", "Failed to check provided key: %v", err) + return fail(ctx, "Key check failed", "Failed to check provided key: %v", err) } switch key.Type { case asymkey_model.KeyTypeDeploy: @@ -164,7 +186,7 @@ func runServ(c *cli.Context) error { words, err := shellquote.Split(cmd) if err != nil { - return fail("Error parsing arguments", "Failed to parse arguments: %v", err) + return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err) } if len(words) < 2 { @@ -175,7 +197,7 @@ func runServ(c *cli.Context) error { return nil } } - return fail("Too few arguments", "Too few arguments in cmd: %s", cmd) + return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) } verb := words[0] @@ -187,7 +209,7 @@ func runServ(c *cli.Context) error { var lfsVerb string if verb == lfsAuthenticateVerb { if !setting.LFS.StartServer { - return fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled") + return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled") } if len(words) > 2 { @@ -200,37 +222,37 @@ func runServ(c *cli.Context) error { rr := strings.SplitN(repoPath, "/", 2) if len(rr) != 2 { - return fail("Invalid repository path", "Invalid repository path: %v", repoPath) + return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) } username := strings.ToLower(rr[0]) reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) if alphaDashDotPattern.MatchString(reponame) { - return fail("Invalid repo name", "Invalid repo name: %s", reponame) + return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) } if c.Bool("enable-pprof") { if err := os.MkdirAll(setting.PprofDataPath, os.ModePerm); err != nil { - return fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err) + return fail(ctx, "Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err) } stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username) if err != nil { - return fail("Internal Server Error", "Unable to start CPU profile: %v", err) + return fail(ctx, "Unable to start CPU profiler", "Unable to start CPU profile: %v", err) } defer func() { stopCPUProfiler() err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username) if err != nil { - _ = fail("Internal Server Error", "Unable to dump Mem Profile: %v", err) + _ = fail(ctx, "Unable to dump Mem profile", "Unable to dump Mem Profile: %v", err) } }() } requestedMode, has := allowedCommands[verb] if !has { - return fail("Unknown git command", "Unknown git command %s", verb) + return fail(ctx, "Unknown git command", "Unknown git command %s", verb) } if verb == lfsAuthenticateVerb { @@ -239,20 +261,13 @@ func runServ(c *cli.Context) error { } else if lfsVerb == "download" { requestedMode = perm.AccessModeRead } else { - return fail("Unknown LFS verb", "Unknown lfs verb %s", lfsVerb) + return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb) } } - results, err := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb) - if err != nil { - if private.IsErrServCommand(err) { - errServCommand := err.(private.ErrServCommand) - if errServCommand.StatusCode != http.StatusInternalServerError { - return fail("Unauthorized", "%s", errServCommand.Error()) - } - return fail("Internal Server Error", "%s", errServCommand.Error()) - } - return fail("Internal Server Error", "%s", err.Error()) + results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error) } // LFS token authentication @@ -274,7 +289,7 @@ func runServ(c *cli.Context) error { // Sign and get the complete encoded token as a string using the secret tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes) if err != nil { - return fail("Internal error", "Failed to sign JWT token: %v", err) + return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err) } tokenAuthentication := &git_model.LFSTokenResponse{ @@ -286,7 +301,7 @@ func runServ(c *cli.Context) error { enc := json.NewEncoder(os.Stdout) err = enc.Encode(tokenAuthentication) if err != nil { - return fail("Internal error", "Failed to encode LFS json response: %v", err) + return fail(ctx, "Failed to encode LFS json response", "Failed to encode LFS json response: %v", err) } return nil } @@ -332,13 +347,13 @@ func runServ(c *cli.Context) error { gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...) if err = gitcmd.Run(); err != nil { - return fail("Internal error", "Failed to execute git command: %v", err) + return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err) } // Update user key activity. if results.KeyID > 0 { if err = private.UpdatePublicKeyInRepo(ctx, results.KeyID, results.RepoID); err != nil { - return fail("Internal error", "UpdatePublicKeyInRepo: %v", err) + return fail(ctx, "Failed to update public key", "UpdatePublicKeyInRepo: %v", err) } } |