code.gitea.io/gitea@v1.22.3/services/doctor/doctor.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package doctor
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"sort"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/models/db"
    14  	"code.gitea.io/gitea/modules/git"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/setting"
    17  	"code.gitea.io/gitea/modules/storage"
    18  )
    19  
    20  // Check represents a Doctor check
    21  type Check struct {
    22  	Title                      string
    23  	Name                       string
    24  	IsDefault                  bool
    25  	Run                        func(ctx context.Context, logger log.Logger, autofix bool) error
    26  	AbortIfFailed              bool
    27  	SkipDatabaseInitialization bool
    28  	Priority                   int
    29  	InitStorage                bool
    30  }
    31  
    32  func initDBSkipLogger(ctx context.Context) error {
    33  	setting.MustInstalled()
    34  	setting.LoadDBSetting()
    35  	if err := db.InitEngine(ctx); err != nil {
    36  		return fmt.Errorf("db.InitEngine: %w", err)
    37  	}
    38  	// some doctor sub-commands need to use git command
    39  	if err := git.InitFull(ctx); err != nil {
    40  		return fmt.Errorf("git.InitFull: %w", err)
    41  	}
    42  	return nil
    43  }
    44  
    45  type doctorCheckLogger struct {
    46  	colorize bool
    47  }
    48  
    49  var _ log.BaseLogger = (*doctorCheckLogger)(nil)
    50  
    51  func (d *doctorCheckLogger) Log(skip int, level log.Level, format string, v ...any) {
    52  	_, _ = fmt.Fprintf(os.Stdout, format+"\n", v...)
    53  }
    54  
    55  func (d *doctorCheckLogger) GetLevel() log.Level {
    56  	return log.TRACE
    57  }
    58  
    59  type doctorCheckStepLogger struct {
    60  	colorize bool
    61  }
    62  
    63  var _ log.BaseLogger = (*doctorCheckStepLogger)(nil)
    64  
    65  func (d *doctorCheckStepLogger) Log(skip int, level log.Level, format string, v ...any) {
    66  	levelChar := fmt.Sprintf("[%s]", strings.ToUpper(level.String()[0:1]))
    67  	var levelArg any = levelChar
    68  	if d.colorize {
    69  		levelArg = log.NewColoredValue(levelChar, level.ColorAttributes()...)
    70  	}
    71  	args := append([]any{levelArg}, v...)
    72  	_, _ = fmt.Fprintf(os.Stdout, " - %s "+format+"\n", args...)
    73  }
    74  
    75  func (d *doctorCheckStepLogger) GetLevel() log.Level {
    76  	return log.TRACE
    77  }
    78  
    79  // Checks is the list of available commands
    80  var Checks []*Check
    81  
    82  // RunChecks runs the doctor checks for the provided list
    83  func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
    84  	SortChecks(checks)
    85  	// the checks output logs by a special logger, they do not use the default logger
    86  	logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
    87  	loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
    88  	dbIsInit := false
    89  	storageIsInit := false
    90  	for i, check := range checks {
    91  		if !dbIsInit && !check.SkipDatabaseInitialization {
    92  			// Only open database after the most basic configuration check
    93  			if err := initDBSkipLogger(ctx); err != nil {
    94  				logger.Error("Error whilst initializing the database: %v", err)
    95  				logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
    96  				return nil
    97  			}
    98  			dbIsInit = true
    99  		}
   100  		if !storageIsInit && check.InitStorage {
   101  			if err := storage.Init(); err != nil {
   102  				logger.Error("Error whilst initializing the storage: %v", err)
   103  				logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
   104  				return nil
   105  			}
   106  			storageIsInit = true
   107  		}
   108  		logger.Info("\n[%d] %s", i+1, check.Title)
   109  		if err := check.Run(ctx, loggerStep, autofix); err != nil {
   110  			if check.AbortIfFailed {
   111  				logger.Critical("FAIL")
   112  				return err
   113  			}
   114  			logger.Error("ERROR")
   115  		} else {
   116  			logger.Info("OK")
   117  		}
   118  	}
   119  	logger.Info("\nAll done (checks: %d).", len(checks))
   120  	return nil
   121  }
   122  
   123  // Register registers a command with the list
   124  func Register(command *Check) {
   125  	Checks = append(Checks, command)
   126  }
   127  
   128  func SortChecks(checks []*Check) {
   129  	sort.SliceStable(checks, func(i, j int) bool {
   130  		if checks[i].Priority == checks[j].Priority {
   131  			return checks[i].Name < checks[j].Name
   132  		}
   133  		if checks[i].Priority == 0 {
   134  			return false
   135  		}
   136  		return checks[i].Priority < checks[j].Priority
   137  	})
   138  }