diff --git a/cmd/repo-health-checker/main.go b/cmd/repo-health-checker/main.go index 0b17e63..64e9230 100644 --- a/cmd/repo-health-checker/main.go +++ b/cmd/repo-health-checker/main.go @@ -1,11 +1,14 @@ package main import ( + "encoding/json" "flag" "fmt" "log/slog" "os" + "strings" + "github.com/joint-online-judge/JOJ3/internal/conf" "github.com/joint-online-judge/JOJ3/pkg/healthcheck" ) @@ -43,6 +46,7 @@ var ( checkFileSumList string metaFile []string gitWhitelist []string + confPath string showVersion *bool Version string ) @@ -58,9 +62,42 @@ func init() { parseMultiValueFlag(&metaFile, "meta", "meta files to check") // TODO: remove gitWhitelist, it is only for backward compatibility now parseMultiValueFlag(&gitWhitelist, "whitelist", "[DEPRECATED] will be ignored") + flag.StringVar(&confPath, "confPath", "", "path to conf file for teapot check") +} + +func prepareTeapotCheck() ( + confObj *conf.Conf, groups []string, actor, repoName string, err error, +) { + actor = os.Getenv("GITHUB_ACTOR") + repository := os.Getenv("GITHUB_REPOSITORY") + if actor == "" || + repository != "" || + strings.Count(repository, "/") != 1 || + confPath != "" { + err = fmt.Errorf("teapot env not set") + return + } + repoParts := strings.Split(repository, "/") + repoName = repoParts[1] + commitMsg, err := conf.GetCommitMsg() + if err != nil { + slog.Error("get commit msg", "error", err) + return + } + conventionalCommit, err := conf.ParseConventionalCommit(commitMsg) + if err != nil { + slog.Error("parse commit msg", "error", err) + return + } + confObj, _, err = conf.ParseConfFile(confPath) + if err != nil { + slog.Error("parse conf", "error", err) + return + } + groups = conf.MatchGroups(confObj, conventionalCommit) + return } -// Generally, err is used for runtime errors, and checkRes is used for the result of the checks. func main() { flag.Parse() if *showVersion { @@ -77,28 +114,19 @@ func main() { "meta", metaFile, ) var err error - err = healthcheck.RepoSize(repoSize) + confObj, groups, actor, repoName, err := prepareTeapotCheck() if err != nil { - fmt.Printf("### Repo Size Check Failed:\n%s\n", err.Error()) + slog.Error("prepare teapot check", "error", err) + confObj = nil } - err = healthcheck.ForbiddenCheck(rootDir) + res := healthcheck.All( + confObj, actor, repoName, rootDir, checkFileNameList, checkFileSumList, + groups, metaFile, repoSize, + ) + jsonRes, err := json.Marshal(res) if err != nil { - fmt.Printf("### Forbidden File Check Failed:\n%s\n", err.Error()) - } - err = healthcheck.MetaCheck(rootDir, metaFile) - if err != nil { - fmt.Printf("### Meta File Check Failed:\n%s\n", err.Error()) - } - err = healthcheck.NonAsciiFiles(rootDir) - if err != nil { - fmt.Printf("### Non-ASCII Characters File Check Failed:\n%s\n", err.Error()) - } - err = healthcheck.NonAsciiMsg(rootDir) - if err != nil { - fmt.Printf("### Non-ASCII Characters Commit Message Check Failed:\n%s\n", err.Error()) - } - err = healthcheck.VerifyFiles(rootDir, checkFileNameList, checkFileSumList) - if err != nil { - fmt.Printf("### Repo File Check Failed:\n%s\n", err.Error()) + slog.Error("marshal result", "error", err) + os.Exit(1) } + fmt.Println(string(jsonRes)) } diff --git a/internal/parser/healthcheck/parser.go b/internal/parser/healthcheck/parser.go index 2ab6d4d..c4c194f 100644 --- a/internal/parser/healthcheck/parser.go +++ b/internal/parser/healthcheck/parser.go @@ -1,10 +1,12 @@ package healthcheck import ( + "encoding/json" "fmt" "github.com/criyle/go-judge/envexec" "github.com/joint-online-judge/JOJ3/internal/stage" + "github.com/joint-online-judge/JOJ3/pkg/healthcheck" ) type Healthcheck struct{} @@ -21,15 +23,28 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) (stage.ParserResult, return stage.ParserResult{ Score: 0, Comment: fmt.Sprintf( - "Unexpected executor status: `%s`\n`stdout`:\n```%s\n```\n`stderr`:\n```%s\n```", - executorResult.Status, stdout, stderr, + "Unexpected executor status: `%s`\n`stderr`:\n```%s\n```\n", + executorResult.Status, stderr, ), }, true } + var res healthcheck.Result + err := json.Unmarshal([]byte(stdout), &res) + if err != nil { + return stage.ParserResult{ + Score: 0, + Comment: fmt.Sprintf( + "Failed to parse result: `%s`\n`stderr`:\n```%s\n```\n", + err, stderr, + ), + }, true + } + comment := res.Msg + forceQuit := res.Failed return stage.ParserResult{ Score: 0, - Comment: stdout, - }, stdout != "" + Comment: comment, + }, forceQuit } func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) ( diff --git a/pkg/healthcheck/all.go b/pkg/healthcheck/all.go new file mode 100644 index 0000000..22dbf33 --- /dev/null +++ b/pkg/healthcheck/all.go @@ -0,0 +1,61 @@ +package healthcheck + +import ( + "fmt" + + "github.com/joint-online-judge/JOJ3/internal/conf" +) + +type Result struct { + Msg string + Failed bool +} + +func All( + confObj *conf.Conf, + actor, repoName, rootDir, checkFileNameList, checkFileSumList string, + groups, metaFile []string, + repoSize float64, +) (res Result) { + var err error + if confObj != nil { + output, err := TeapotCheck(confObj, actor, repoName, groups) + if err != nil { + res.Msg += fmt.Sprintf("### Teapot Check Failed:\n%s\n", output) + res.Failed = true + } else { + res.Msg += fmt.Sprintf("### Teapot Check Result:\n%s\n", output) + } + } + err = RepoSize(repoSize) + if err != nil { + res.Msg += fmt.Sprintf("### Repo Size Check Failed:\n%s\n", err.Error()) + res.Failed = true + } + err = ForbiddenCheck(rootDir) + if err != nil { + res.Msg += fmt.Sprintf("### Forbidden File Check Failed:\n%s\n", err.Error()) + res.Failed = true + } + err = MetaCheck(rootDir, metaFile) + if err != nil { + res.Msg += fmt.Sprintf("### Meta File Check Failed:\n%s\n", err.Error()) + res.Failed = true + } + err = NonAsciiFiles(rootDir) + if err != nil { + res.Msg += fmt.Sprintf("### Non-ASCII Characters File Check Failed:\n%s\n", err.Error()) + res.Failed = true + } + err = NonAsciiMsg(rootDir) + if err != nil { + res.Msg += fmt.Sprintf("### Non-ASCII Characters Commit Message Check Failed:\n%s\n", err.Error()) + res.Failed = true + } + err = VerifyFiles(rootDir, checkFileNameList, checkFileSumList) + if err != nil { + res.Msg += fmt.Sprintf("### Repo File Check Failed:\n%s\n", err.Error()) + res.Failed = true + } + return +} diff --git a/cmd/teapot-checker/main.go b/pkg/healthcheck/teapot.go similarity index 53% rename from cmd/teapot-checker/main.go rename to pkg/healthcheck/teapot.go index a24c7a0..5d9be45 100644 --- a/cmd/teapot-checker/main.go +++ b/pkg/healthcheck/teapot.go @@ -1,16 +1,14 @@ -package main +package healthcheck import ( "bytes" "encoding/json" - "flag" "fmt" "log/slog" "os" "os/exec" "strings" - "github.com/joint-online-judge/JOJ3/cmd/joj3/env" "github.com/joint-online-judge/JOJ3/internal/conf" ) @@ -21,20 +19,9 @@ type CheckResult struct { TimePeriod int `json:"time_period"` } -func check(conf *conf.Conf) (checkResults []CheckResult, err error) { +func runTeapot(conf *conf.Conf, actor, repoName string) (checkResults []CheckResult, err error) { os.Setenv("LOG_FILE_PATH", conf.Teapot.LogPath) os.Setenv("_TYPER_STANDARD_TRACEBACK", "1") - actor := os.Getenv("GITHUB_ACTOR") - repository := os.Getenv("GITHUB_REPOSITORY") - if actor == "" || - repository == "" || - strings.Count(repository, "/") != 1 { - slog.Error("teapot env not set") - err = fmt.Errorf("teapot env not set") - return - } - repoParts := strings.Split(env.Attr.Repository, "/") - repoName := repoParts[1] var formattedGroups []string for _, group := range conf.Teapot.Groups { groupConfig := fmt.Sprintf("%s=%d:%d", @@ -56,7 +43,7 @@ func check(conf *conf.Conf) (checkResults []CheckResult, err error) { slog.Error("teapot check exec", "error", err) return } - if json.Unmarshal(stdoutBuf.Bytes(), &checkResults) != nil { + if err = json.Unmarshal(stdoutBuf.Bytes(), &checkResults); err != nil { slog.Error("unmarshal teapot check result", "error", err, "stdout", stdoutBuf.String()) return @@ -86,73 +73,35 @@ func generateOutput( } } comment += fmt.Sprintf( - "in last %d hour(s): submit count %d, max count %d\n", + "in last %d hour(s): submit count %d, max count %d", checkResult.TimePeriod, checkResult.SubmitCount, checkResult.MaxCount, ) if useGroup && checkResult.SubmitCount+1 > checkResult.MaxCount { - err = fmt.Errorf("submit count exceeded") + err = fmt.Errorf( + "keyword `%s` submit count exceeded", + checkResult.Name, + ) + comment += ", exceeded" } + comment += "\n" } return } -func setupSlog() { - opts := &slog.HandlerOptions{} - handler := slog.NewTextHandler(os.Stderr, opts) - logger := slog.New(handler) - slog.SetDefault(logger) -} - -var ( - confPath string - Version string = "debug" -) - -func mainImpl() (err error) { - showVersion := flag.Bool("version", false, "print current version") - flag.StringVar(&confPath, "conf-path", "./conf.json", "path for config file") - flag.Parse() - if *showVersion { - fmt.Println(Version) - return - } - setupSlog() - slog.Info("start teapot-checker", "version", Version) - commitMsg, err := conf.GetCommitMsg() - if err != nil { - slog.Error("get commit msg", "error", err) - return - } - conventionalCommit, err := conf.ParseConventionalCommit(commitMsg) - if err != nil { - slog.Error("parse commit msg", "error", err) - return - } - confObj, _, err := conf.ParseConfFile(confPath) - if err != nil { - slog.Error("parse conf", "error", err) - return - } - groups := conf.MatchGroups(confObj, conventionalCommit) - checkResults, err := check(confObj) +func TeapotCheck( + conf *conf.Conf, actor, repoName string, groups []string, +) (output string, err error) { + checkResults, err := runTeapot(conf, actor, repoName) if err != nil { slog.Error("teapot check", "error", err) return } - output, err := generateOutput(checkResults, groups) + output, err = generateOutput(checkResults, groups) if err != nil { slog.Error("generate output", "error", err) return } - fmt.Println(output) return } - -func main() { - if err := mainImpl(); err != nil { - slog.Error("main exit", "error", err) - os.Exit(1) - } -}