diff --git a/checkers/checker.go b/checkers/checker.go new file mode 100644 index 0000000..a0eb7a0 --- /dev/null +++ b/checkers/checker.go @@ -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 +} diff --git a/checkers/debian.go b/checkers/debian.go new file mode 100644 index 0000000..b8322d8 --- /dev/null +++ b/checkers/debian.go @@ -0,0 +1,10 @@ +package checkers + +var DebianProject Project = Project{ + Name: "debian", + Properties: DefaultProjectProperties, + NumOfCheckers: 1, + Checkers: []*ProjectChecker{ + GetDefaultChecker("debian"), + }, +} diff --git a/checkers/default.go b/checkers/default.go new file mode 100644 index 0000000..277b363 --- /dev/null +++ b/checkers/default.go @@ -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 + }, + } +} diff --git a/config/config.go b/config/config.go index 84b1f67..fb02a9a 100644 --- a/config/config.go +++ b/config/config.go @@ -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). diff --git a/main.go b/main.go index cc26f09..29095a2 100644 --- a/main.go +++ b/main.go @@ -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