summaryrefslogtreecommitdiffstats
path: root/services/doctor/doctor.go
blob: a4eb5e16b91c53a480f319828d6de6848fc50bb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package doctor

import (
	"context"
	"fmt"
	"os"
	"sort"
	"strings"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/storage"
)

// Check represents a Doctor check
type Check struct {
	Title                      string
	Name                       string
	IsDefault                  bool
	Run                        func(ctx context.Context, logger log.Logger, autofix bool) error
	AbortIfFailed              bool
	SkipDatabaseInitialization bool
	Priority                   int
	InitStorage                bool
}

func initDBSkipLogger(ctx context.Context) error {
	setting.MustInstalled()
	setting.LoadDBSetting()
	if err := db.InitEngine(ctx); err != nil {
		return fmt.Errorf("db.InitEngine: %w", err)
	}
	// some doctor sub-commands need to use git command
	if err := git.InitFull(ctx); err != nil {
		return fmt.Errorf("git.InitFull: %w", err)
	}
	return nil
}

type doctorCheckLogger struct {
	colorize bool
}

var _ log.BaseLogger = (*doctorCheckLogger)(nil)

func (d *doctorCheckLogger) Log(skip int, level log.Level, format string, v ...any) {
	_, _ = fmt.Fprintf(os.Stdout, format+"\n", v...)
}

func (d *doctorCheckLogger) GetLevel() log.Level {
	return log.TRACE
}

type doctorCheckStepLogger struct {
	colorize bool
}

var _ log.BaseLogger = (*doctorCheckStepLogger)(nil)

func (d *doctorCheckStepLogger) Log(skip int, level log.Level, format string, v ...any) {
	levelChar := fmt.Sprintf("[%s]", strings.ToUpper(level.String()[0:1]))
	var levelArg any = levelChar
	if d.colorize {
		levelArg = log.NewColoredValue(levelChar, level.ColorAttributes()...)
	}
	args := append([]any{levelArg}, v...)
	_, _ = fmt.Fprintf(os.Stdout, " - %s "+format+"\n", args...)
}

func (d *doctorCheckStepLogger) GetLevel() log.Level {
	return log.TRACE
}

// Checks is the list of available commands
var Checks []*Check

// RunChecks runs the doctor checks for the provided list
func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
	SortChecks(checks)
	// the checks output logs by a special logger, they do not use the default logger
	logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
	loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
	dbIsInit := false
	storageIsInit := false
	for i, check := range checks {
		if !dbIsInit && !check.SkipDatabaseInitialization {
			// Only open database after the most basic configuration check
			if err := initDBSkipLogger(ctx); err != nil {
				logger.Error("Error whilst initializing the database: %v", err)
				logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
				return nil
			}
			dbIsInit = true
		}
		if !storageIsInit && check.InitStorage {
			if err := storage.Init(); err != nil {
				logger.Error("Error whilst initializing the storage: %v", err)
				logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
				return nil
			}
			storageIsInit = true
		}
		logger.Info("\n[%d] %s", i+1, check.Title)
		if err := check.Run(ctx, loggerStep, autofix); err != nil {
			if check.AbortIfFailed {
				logger.Critical("FAIL")
				return err
			}
			logger.Error("ERROR")
		} else {
			logger.Info("OK")
		}
	}
	logger.Info("\nAll done (checks: %d).", len(checks))
	return nil
}

// Register registers a command with the list
func Register(command *Check) {
	Checks = append(Checks, command)
}

func SortChecks(checks []*Check) {
	sort.SliceStable(checks, func(i, j int) bool {
		if checks[i].Priority == checks[j].Priority {
			return checks[i].Name < checks[j].Name
		}
		if checks[i].Priority == 0 {
			return false
		}
		return checks[i].Priority < checks[j].Priority
	})
}