write checker framework

Signed-off-by: Nathan13888 <29968201+Nathan13888@users.noreply.github.com>
This commit is contained in:
Nathan Chung 2023-06-09 03:03:28 -04:00
parent 4823e28fe0
commit 4502ee4171
Signed by: n4chung
SSH Key Fingerprint: SHA256:/+NqYA5MfQIjjfwa4z16mw3Y+lxgY/Ml8wCeGnh6qBU
5 changed files with 228 additions and 18 deletions

115
checkers/checker.go Normal file
View File

@ -0,0 +1,115 @@
package checkers
import (
"errors"
"time"
"github.com/rs/zerolog/log"
)
// "Projects" which have valid configs.
var EnabledProjects = make(map[string]*Project)
// TODO: refactor all errors into variables
////////////////////////////////////////////////////////////////////////////////
// Project, Project Properties, Project Checker
type Project struct {
Name string
Properties ProjectProperties
NumOfCheckers int
Checkers []*ProjectChecker
}
type ProjectProperties struct {
OOSSince time.Time //`json:"out_of_sync_since"`
OOSInterval int64 `json:"out_of_sync_interval"` // in seconds
CSC string `json:"csc"`
Upstream string `json:"upstream"`
File string `json:"file"`
}
type ProjectChecker struct {
CheckProject func() CheckerResult
}
// //////////////////////////////////////////////////////////////////////////////
// Checker Status and Checker Result
type CheckerStatus string
var CHECKER_SUCCESS CheckerStatus = "success"
var CHECKER_PROGRESS CheckerStatus = "progress"
var CHECKER_ERROR CheckerStatus = "error"
var CHECKER_FAIL CheckerStatus = "fail"
type CheckerResult struct {
ProjectName string `json:"project_name"`
CheckerName string `json:"checker_name"`
Time time.Time `json:"time"`
Status CheckerStatus `json:"status"`
Error error `json:"error"`
}
type CheckerResultCallback func(CheckerResult)
////////////////////////////////////////////////////////////////////////////////
// Given a project, all checks are initiated.
func (p Project) RunChecksAsync(callback CheckerResultCallback) error {
checks := p.Checkers
n := len(checks)
// TODO: assert other checkers?
// assert if the number of checkers match the expected number of checkers
if n != p.NumOfCheckers {
return errors.New("Number of checkers does not match the expected number of checkers.")
}
log.Info().Msgf("Running %d checks for project %s", n, p.Name)
for i := 0; i < n; i++ {
check := checks[i]
log.Info().Str("project", p.Name).Msgf("Running check %d", i)
check.RunCheckAsync(callback)
}
return nil
}
// Given a check, run the check asynchronously and call the callback function.
func (c ProjectChecker) RunCheckAsync(callback CheckerResultCallback) error {
go func() {
res := c.CheckProject()
callback(res)
}()
return nil
}
////////////////////////////////////////////////////////////////////////////////
// Looks up a project by name, and returns only if the project is enabled.
func GetProject(name string) (*Project, error) {
res, exists := EnabledProjects[name]
if !exists {
return res, errors.New("Requested project not found.")
}
return res, nil
}
// Loads a project by name, and returns if the project is found.
func LoadProject(name string) (*Project, error) {
res, exists := SupportedProjects[name]
if !exists {
return res, errors.New("Requested project not found.")
}
if res.Properties == DefaultProjectProperties {
log.Debug().Str("csc", res.Properties.CSC).
Msgf("Requested project %s has default properties.", name)
return res, errors.New("Requested project has invalid properties. Please check the project config.")
}
log.Debug().Msgf("Loading Project %s", name)
EnabledProjects[name] = res
return res, nil
}

10
checkers/debian.go Normal file
View File

@ -0,0 +1,10 @@
package checkers
var DebianProject Project = Project{
Name: "debian",
Properties: DefaultProjectProperties,
NumOfCheckers: 1,
Checkers: []*ProjectChecker{
GetDefaultChecker("debian"),
},
}

48
checkers/default.go Normal file
View File

@ -0,0 +1,48 @@
package checkers
import (
"time"
"github.com/rs/zerolog/log"
)
////////////////////////////////////////////////////////////////////////////////
var DefaultProjectProperties = ProjectProperties{
OOSSince: time.Now(),
OOSInterval: 3600,
CSC: "unknown",
Upstream: "unknown",
File: "unknown",
}
var DefaultCheckerResult CheckerResult = CheckerResult{
ProjectName: "unknown",
CheckerName: "unknown",
Status: CHECKER_PROGRESS,
}
////////////////////////////////////////////////////////////////////////////////
// All "projects" which have been implemented.
var SupportedProjects map[string]*Project = map[string]*Project{
"debian": &DebianProject,
}
func LoadDefaultProjects() {
// Load all projects that's implemented
log.Info().Msg("Loading Default Projects.")
LoadProject("debian")
}
func GetDefaultChecker(name string) *ProjectChecker {
return &ProjectChecker{
CheckProject: func() CheckerResult {
// TODO: implement
return DefaultCheckerResult
},
}
}

View File

@ -34,6 +34,14 @@ func LoadFromFile(path string) error {
for proj, prop := range data {
log.Info().Str("project", proj).Msg("Enabled Project.")
sp, exists := checkers.SupportedProjects[NormalizeName(proj)]
if !exists {
log.Warn().Str("project", proj).Msg("Project not supported.")
continue
}
sp.Properties = prop
log.Debug().
Str("distribution", proj).
// Time("out_of_sync_since", prop.OOSSince).

65
main.go
View File

@ -2,7 +2,6 @@ package main
import (
"os"
"path/filepath"
"github.com/Nathan13888/mirror-checker2/v2/checkers"
"github.com/Nathan13888/mirror-checker2/v2/config"
@ -11,17 +10,20 @@ import (
"github.com/urfave/cli/v2"
)
func getAbsPath(path string) string {
absPath, err := filepath.Abs(path)
var configPath string
func loadConfig() {
path := config.GetAbsPath(configPath)
// TODO: check if file exists
log.Info().Str("path", path).Msg("Loading config file.")
err := config.LoadFromFile(path)
if err != nil {
log.Error().Err(err).Msg("Failed to get absolute path.")
return path
log.Fatal().Err(err).Msg("Failed to load config file.")
}
return absPath
}
func main() {
var err error
config.SetupLogger(true) // TODO: flag for debug mode
// Start CLI
@ -33,6 +35,16 @@ func main() {
EnableBashCompletion: true,
// https://cli.urfave.org/v2/examples/combining-short-options/
// TODO: flags for config file (mirrors.json), defaults to mirrors.json
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "path to config file",
Destination: &configPath,
Value: "data/mirrors.json",
EnvVars: []string{"MC_CONFIG_FILE"},
},
},
Commands: []*cli.Command{
{
Name: "daemon",
@ -45,12 +57,7 @@ func main() {
// TODO: enable all projects by default
// checkers.LoadDefaultProjects()
path := getAbsPath("data/mirrors.json")
log.Info().Str("path", path).Msg("Loading config file.")
err = config.LoadFromFile(path)
if err != nil {
log.Fatal().Err(err).Msg("Failed to load config file.")
}
loadConfig()
return web.StartServer()
},
@ -61,14 +68,22 @@ func main() {
Usage: "checks particular mirror (once)",
Action: func(cCtx *cli.Context) error {
checkers.LoadDefaultProjects()
loadConfig()
// verify all projects are enabled
// TODO:
// TODO: ???
// attempt to look up and check all projects
for _, arg := range cCtx.Args().Slice() {
log.Printf("\nPulling project information for '%s'\n\n", arg)
proj, err := checkers.GetProject(arg)
projects := cCtx.Args().Slice()
if len(projects) == 0 {
log.Fatal().Msg("No projects specified.")
}
log.Debug().Msgf("Checking all specified projects.")
for _, arg := range projects {
log.Info().Msgf("Pulling project information for '%s'.", arg)
proj, err := checkers.LoadProject(arg)
if err != nil {
log.Fatal().Err(err).
Str("project", arg).
@ -76,7 +91,21 @@ func main() {
return err
}
proj.RunChecksAsync()
err = proj.RunChecksAsync(func(res checkers.CheckerResult) {
if res.Error != nil {
log.Error().Err(res.Error).
Time("time", res.Time).
Msgf("Failed check %s for project %s", res.CheckerName, res.ProjectName)
} else {
log.Info().
Time("time", res.Time).
Str("status", string(res.Status)).
Msgf("Completed check %s for project %s", res.CheckerName, res.ProjectName)
}
})
if err != nil {
log.Fatal().Err(err).Msg("Failed to check project.")
}
}
return nil