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>
This commit is contained in:
wxiaoguang 2023-03-29 14:32:26 +08:00 committed by GitHub
parent 79e7a6ec1e
commit f4538791f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 547 additions and 851 deletions

View file

@ -60,13 +60,13 @@ func main() {
// generate data
buf, err := generate()
if err != nil {
log.Fatal(err)
log.Fatalf("generate err: %v", err)
}
// write
err = os.WriteFile(*flagOut, buf, 0o644)
if err != nil {
log.Fatal(err)
log.Fatalf("WriteFile err: %v", err)
}
}

View file

@ -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

View file

@ -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())
}

View file

@ -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

View file

@ -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
}

View file

@ -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)
}

View file

@ -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"))
setup(ctx, c.Bool("debug"))
statusCode, msg := private.SetLogSQL(ctx, !c.Bool("off"))
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
extra := private.SetLogSQL(ctx, !c.Bool("off"))
return handleCliResponseExtra(extra)
}

View file

@ -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)
}

View file

@ -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,32 +94,54 @@ 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)
}
}
ctx, cancel := installSignals()
defer cancel()
if len(logMessage) > 0 {
_ = private.SSHLog(ctx, true, fmt.Sprintf(logMessage+": ", args...))
if userMessage != "" {
if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) {
logMsg = userMessage + " " + logMsg
} else {
logMsg = userMessage + ". " + logMsg
}
}
_ = private.SSHLog(ctx, true, logMsg)
}
return cli.NewExitError("", 1)
}
// 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)
}
if extra.HasError() {
return cli.NewExitError(extra.Error, 1)
}
return nil
}
func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
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)
}
}

View file

@ -8,6 +8,7 @@ import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
@ -68,6 +69,11 @@ func (r *Request) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *Re
return r
}
func (r *Request) SetReadWriteTimeout(readWriteTimeout time.Duration) *Request {
r.setting.ReadWriteTimeout = readWriteTimeout
return r
}
// SetTLSClientConfig sets tls connection configurations if visiting https url.
func (r *Request) SetTLSClientConfig(config *tls.Config) *Request {
r.setting.TLSClientConfig = config
@ -138,11 +144,11 @@ func (r *Request) getResponse() (*http.Response, error) {
r.Body(paramBody)
}
url, err := url.Parse(r.url)
var err error
r.req.URL, err = url.Parse(r.url)
if err != nil {
return nil, err
}
r.req.URL = url
trans := r.setting.Transport
if trans == nil {
@ -194,3 +200,7 @@ func TimeoutDialer(cTimeout time.Duration) func(ctx context.Context, net, addr s
return conn, nil
}
}
func (r *Request) GoString() string {
return fmt.Sprintf("%s %s", r.req.Method, r.url)
}

View file

@ -5,14 +5,11 @@ package private
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
)
@ -99,126 +96,46 @@ type HookProcReceiveRefResult struct {
}
// HookPreReceive check whether the provided commits are allowed
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
)
req := newInternalRequest(ctx, reqURL, "POST")
req = req.Header("Content-Type", "application/json")
jsonBytes, _ := json.Marshal(opts)
req.Body(jsonBytes)
req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
resp, err := req.Response()
if err != nil {
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return resp.StatusCode, decodeJSONError(resp).Err
}
return http.StatusOK, ""
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequest(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
_, extra := requestJSONResp(req, &responseText{})
return extra
}
// HookPostReceive updates services and users
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
)
req := newInternalRequest(ctx, reqURL, "POST")
req = req.Header("Content-Type", "application/json")
req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
jsonBytes, _ := json.Marshal(opts)
req.Body(jsonBytes)
resp, err := req.Response()
if err != nil {
return nil, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, decodeJSONError(resp).Err
}
res := &HookPostReceiveResult{}
_ = json.NewDecoder(resp.Body).Decode(res)
return res, ""
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequest(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
return requestJSONResp(req, &HookPostReceiveResult{})
}
// HookProcReceive proc-receive hook
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
)
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequest(ctx, reqURL, "POST")
req = req.Header("Content-Type", "application/json")
req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
jsonBytes, _ := json.Marshal(opts)
req.Body(jsonBytes)
resp, err := req.Response()
if err != nil {
return nil, fmt.Errorf("Unable to contact gitea: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(decodeJSONError(resp).Err)
}
res := &HookProcReceiveResult{}
_ = json.NewDecoder(resp.Body).Decode(res)
return res, nil
req := newInternalRequest(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
return requestJSONResp(req, &HookProcReceiveResult{})
}
// SetDefaultBranch will set the default branch to the provided branch for the provided repository
func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) error {
func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) ResponseExtra {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
url.PathEscape(branch),
)
req := newInternalRequest(ctx, reqURL, "POST")
req = req.Header("Content-Type", "application/json")
req.SetTimeout(60*time.Second, 60*time.Second)
resp, err := req.Response()
if err != nil {
return fmt.Errorf("Unable to contact gitea: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err)
}
return nil
return requestJSONUserMsg(req, "")
}
// SSHLog sends ssh error log response
func SSHLog(ctx context.Context, isErr bool, msg string) error {
reqURL := setting.LocalURL + "api/internal/ssh/log"
req := newInternalRequest(ctx, reqURL, "POST")
req = req.Header("Content-Type", "application/json")
jsonBytes, _ := json.Marshal(&SSHLogOption{
IsError: isErr,
Message: msg,
})
req.Body(jsonBytes)
req.SetTimeout(60*time.Second, 60*time.Second)
resp, err := req.Response()
if err != nil {
return fmt.Errorf("unable to contact gitea: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err)
}
return nil
req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
_, extra := requestJSONResp(req, &responseText{})
return extra.Error
}