Skip to content

Commit 4569339

Browse files
zeripath6543techknowlogick
authored
Refactor doctor (#12264)
* Refactor Logger Refactor Logger to make a logger interface and make it possible to wrap loggers for specific purposes. * Refactor Doctor Move the gitea doctor functions into its own module. Use a logger for its messages instead of returning a results string[] Signed-off-by: Andrew Thornton <[email protected]> * Update modules/doctor/misc.go Co-authored-by: 6543 <[email protected]> * Update modules/doctor/misc.go Co-authored-by: 6543 <[email protected]> Co-authored-by: 6543 <[email protected]> Co-authored-by: techknowlogick <[email protected]>
1 parent 253add8 commit 4569339

File tree

8 files changed

+786
-521
lines changed

8 files changed

+786
-521
lines changed

cmd/doctor.go

+35-521
Large diffs are not rendered by default.

modules/doctor/authorizedkeys.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package doctor
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"fmt"
11+
"os"
12+
"path/filepath"
13+
"strings"
14+
15+
"code.gitea.io/gitea/models"
16+
"code.gitea.io/gitea/modules/log"
17+
"code.gitea.io/gitea/modules/setting"
18+
)
19+
20+
const tplCommentPrefix = `# gitea public key`
21+
22+
func checkAuthorizedKeys(logger log.Logger, autofix bool) error {
23+
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
24+
return nil
25+
}
26+
27+
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
28+
f, err := os.Open(fPath)
29+
if err != nil {
30+
if !autofix {
31+
logger.Critical("Unable to open authorized_keys file. ERROR: %v", err)
32+
return fmt.Errorf("Unable to open authorized_keys file. ERROR: %v", err)
33+
}
34+
logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err)
35+
if err = models.RewriteAllPublicKeys(); err != nil {
36+
logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
37+
return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %v", err)
38+
}
39+
}
40+
defer f.Close()
41+
42+
linesInAuthorizedKeys := map[string]bool{}
43+
44+
scanner := bufio.NewScanner(f)
45+
for scanner.Scan() {
46+
line := scanner.Text()
47+
if strings.HasPrefix(line, tplCommentPrefix) {
48+
continue
49+
}
50+
linesInAuthorizedKeys[line] = true
51+
}
52+
f.Close()
53+
54+
// now we regenerate and check if there are any lines missing
55+
regenerated := &bytes.Buffer{}
56+
if err := models.RegeneratePublicKeys(regenerated); err != nil {
57+
logger.Critical("Unable to regenerate authorized_keys file. ERROR: %v", err)
58+
return fmt.Errorf("Unable to regenerate authorized_keys file. ERROR: %v", err)
59+
}
60+
scanner = bufio.NewScanner(regenerated)
61+
for scanner.Scan() {
62+
line := scanner.Text()
63+
if strings.HasPrefix(line, tplCommentPrefix) {
64+
continue
65+
}
66+
if ok := linesInAuthorizedKeys[line]; ok {
67+
continue
68+
}
69+
if !autofix {
70+
logger.Critical(
71+
"authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"",
72+
fPath,
73+
"gitea admin regenerate keys",
74+
"gitea doctor --run authorized_keys --fix")
75+
return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized_keys --fix"`)
76+
}
77+
logger.Warn("authorized_keys is out of date. Attempting rewrite...")
78+
err = models.RewriteAllPublicKeys()
79+
if err != nil {
80+
logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
81+
return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %v", err)
82+
}
83+
}
84+
return nil
85+
}
86+
87+
func init() {
88+
Register(&Check{
89+
Title: "Check if OpenSSH authorized_keys file is up-to-date",
90+
Name: "authorized-keys",
91+
IsDefault: true,
92+
Run: checkAuthorizedKeys,
93+
Priority: 4,
94+
})
95+
}

modules/doctor/dbconsistency.go

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package doctor
6+
7+
import (
8+
"context"
9+
10+
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/models/migrations"
12+
"code.gitea.io/gitea/modules/log"
13+
)
14+
15+
func checkDBConsistency(logger log.Logger, autofix bool) error {
16+
// make sure DB version is uptodate
17+
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
18+
logger.Critical("Model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded")
19+
return err
20+
}
21+
22+
// find labels without existing repo or org
23+
count, err := models.CountOrphanedLabels()
24+
if err != nil {
25+
logger.Critical("Error: %v whilst counting orphaned labels")
26+
return err
27+
}
28+
29+
if count > 0 {
30+
if autofix {
31+
if err = models.DeleteOrphanedLabels(); err != nil {
32+
logger.Critical("Error: %v whilst deleting orphaned labels")
33+
return err
34+
}
35+
logger.Info("%d labels without existing repository/organisation deleted", count)
36+
} else {
37+
logger.Warn("%d labels without existing repository/organisation", count)
38+
}
39+
}
40+
41+
// find issues without existing repository
42+
count, err = models.CountOrphanedIssues()
43+
if err != nil {
44+
logger.Critical("Error: %v whilst counting orphaned issues")
45+
return err
46+
}
47+
if count > 0 {
48+
if autofix {
49+
if err = models.DeleteOrphanedIssues(); err != nil {
50+
logger.Critical("Error: %v whilst deleting orphaned issues")
51+
return err
52+
}
53+
logger.Info("%d issues without existing repository deleted", count)
54+
} else {
55+
logger.Warn("%d issues without existing repository", count)
56+
}
57+
}
58+
59+
// find pulls without existing issues
60+
count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
61+
if err != nil {
62+
logger.Critical("Error: %v whilst counting orphaned objects")
63+
return err
64+
}
65+
if count > 0 {
66+
if autofix {
67+
if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil {
68+
logger.Critical("Error: %v whilst deleting orphaned objects")
69+
return err
70+
}
71+
logger.Info("%d pull requests without existing issue deleted", count)
72+
} else {
73+
logger.Warn("%d pull requests without existing issue", count)
74+
}
75+
}
76+
77+
// find tracked times without existing issues/pulls
78+
count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id")
79+
if err != nil {
80+
logger.Critical("Error: %v whilst counting orphaned objects")
81+
return err
82+
}
83+
if count > 0 {
84+
if autofix {
85+
if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil {
86+
logger.Critical("Error: %v whilst deleting orphaned objects")
87+
return err
88+
}
89+
logger.Info("%d tracked times without existing issue deleted", count)
90+
} else {
91+
logger.Warn("%d tracked times without existing issue", count)
92+
}
93+
}
94+
95+
// find null archived repositories
96+
count, err = models.CountNullArchivedRepository()
97+
if err != nil {
98+
logger.Critical("Error: %v whilst counting null archived repositories")
99+
return err
100+
}
101+
if count > 0 {
102+
if autofix {
103+
updatedCount, err := models.FixNullArchivedRepository()
104+
if err != nil {
105+
logger.Critical("Error: %v whilst fixing null archived repositories")
106+
return err
107+
}
108+
logger.Info("%d repositories with null is_archived updated", updatedCount)
109+
} else {
110+
logger.Warn("%d repositories with null is_archived", count)
111+
}
112+
}
113+
114+
// TODO: function to recalc all counters
115+
116+
return nil
117+
}
118+
119+
func init() {
120+
Register(&Check{
121+
Title: "Check consistency of database",
122+
Name: "check-db-consistency",
123+
IsDefault: false,
124+
Run: checkDBConsistency,
125+
Priority: 3,
126+
})
127+
}

modules/doctor/dbversion.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package doctor
6+
7+
import (
8+
"context"
9+
10+
"code.gitea.io/gitea/models"
11+
"code.gitea.io/gitea/models/migrations"
12+
"code.gitea.io/gitea/modules/log"
13+
)
14+
15+
func checkDBVersion(logger log.Logger, autofix bool) error {
16+
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
17+
if !autofix {
18+
logger.Critical("Error: %v during ensure up to date", err)
19+
return err
20+
}
21+
logger.Warn("Got Error: %v during ensure up to date", err)
22+
logger.Warn("Attempting to migrate to the latest DB version to fix this.")
23+
24+
err = models.NewEngine(context.Background(), migrations.Migrate)
25+
if err != nil {
26+
logger.Critical("Error: %v during migration")
27+
}
28+
return err
29+
}
30+
return nil
31+
}
32+
33+
func init() {
34+
Register(&Check{
35+
Title: "Check Database Version",
36+
Name: "check-db-version",
37+
IsDefault: true,
38+
Run: checkDBVersion,
39+
AbortIfFailed: false,
40+
Priority: 2,
41+
})
42+
}

modules/doctor/doctor.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package doctor
6+
7+
import (
8+
"fmt"
9+
"sort"
10+
"strings"
11+
12+
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/setting"
15+
)
16+
17+
// Check represents a Doctor check
18+
type Check struct {
19+
Title string
20+
Name string
21+
IsDefault bool
22+
Run func(logger log.Logger, autofix bool) error
23+
AbortIfFailed bool
24+
SkipDatabaseInitialization bool
25+
Priority int
26+
}
27+
28+
type wrappedLevelLogger struct {
29+
log.LevelLogger
30+
}
31+
32+
func (w *wrappedLevelLogger) Log(skip int, level log.Level, format string, v ...interface{}) error {
33+
return w.LevelLogger.Log(
34+
skip+1,
35+
level,
36+
" - %s "+format,
37+
append(
38+
[]interface{}{
39+
log.NewColoredValueBytes(
40+
fmt.Sprintf("[%s]", strings.ToUpper(level.String()[0:1])),
41+
level.Color()),
42+
}, v...)...)
43+
}
44+
45+
func initDBDisableConsole(disableConsole bool) error {
46+
setting.NewContext()
47+
setting.InitDBConfig()
48+
49+
setting.NewXORMLogService(disableConsole)
50+
if err := models.SetEngine(); err != nil {
51+
return fmt.Errorf("models.SetEngine: %v", err)
52+
}
53+
return nil
54+
}
55+
56+
// Checks is the list of available commands
57+
var Checks []*Check
58+
59+
// RunChecks runs the doctor checks for the provided list
60+
func RunChecks(logger log.Logger, autofix bool, checks []*Check) error {
61+
wrappedLogger := log.LevelLoggerLogger{
62+
LevelLogger: &wrappedLevelLogger{logger},
63+
}
64+
65+
dbIsInit := false
66+
for i, check := range checks {
67+
if !dbIsInit && !check.SkipDatabaseInitialization {
68+
// Only open database after the most basic configuration check
69+
setting.EnableXORMLog = false
70+
if err := initDBDisableConsole(true); err != nil {
71+
logger.Error("Error whilst initializing the database: %v", err)
72+
logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
73+
return nil
74+
}
75+
dbIsInit = true
76+
}
77+
logger.Info("[%d] %s", log.NewColoredIDValue(i+1), check.Title)
78+
logger.Flush()
79+
if err := check.Run(&wrappedLogger, autofix); err != nil {
80+
if check.AbortIfFailed {
81+
logger.Critical("FAIL")
82+
return err
83+
}
84+
logger.Error("ERROR")
85+
} else {
86+
logger.Info("OK")
87+
logger.Flush()
88+
}
89+
}
90+
return nil
91+
}
92+
93+
// Register registers a command with the list
94+
func Register(command *Check) {
95+
Checks = append(Checks, command)
96+
sort.SliceStable(Checks, func(i, j int) bool {
97+
if Checks[i].Priority == Checks[j].Priority {
98+
return Checks[i].Name < Checks[j].Name
99+
}
100+
if Checks[i].Priority == 0 {
101+
return false
102+
}
103+
return Checks[i].Priority < Checks[j].Priority
104+
})
105+
}

0 commit comments

Comments
 (0)