diff --git a/cmd/teapot-checker/main.go b/cmd/teapot-checker/main.go new file mode 100644 index 0000000..59e44ca --- /dev/null +++ b/cmd/teapot-checker/main.go @@ -0,0 +1,128 @@ +package main + +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" +) + +type CheckResult struct { + Name string `json:"name"` + SubmitCount int `json:"submit_count"` + MaxCount int `json:"max_count"` + TimePeriod int `json:"time_period"` +} + +func check(conf *conf.Conf) (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", + group.Name, group.MaxCount, group.TimePeriodHour) + formattedGroups = append(formattedGroups, groupConfig) + } + args := []string{ + "joj3-check", conf.Teapot.EnvFilePath, + actor, conf.Teapot.GradingRepoName, repoName, + conf.Teapot.ScoreboardPath, conf.Name, + "--group-config", strings.Join(formattedGroups, ","), + } + var stdoutBuf, stderrBuf bytes.Buffer + cmd := exec.Command("joint-teapot", args...) // #nosec G204 + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + err = cmd.Run() + if err != nil { + slog.Error("teapot check exec", "error", err) + return + } + if json.Unmarshal(stdoutBuf.Bytes(), &checkResults) != nil { + slog.Error("unmarshal teapot check result", "error", err, + "stdout", stdoutBuf.String()) + return + } + slog.Info("teapot check result", "result", checkResults) + return +} + +func generateOutput(checkResults []CheckResult) (comment string, err error) { + if len(checkResults) == 0 { + return + } + for _, checkResult := range checkResults { + if checkResult.Name != "" { + comment += fmt.Sprintf("keyword `%s` ", checkResult.Name) + } + comment += fmt.Sprintf( + "in last %d hour(s): submit count %d, max count %d\n", + checkResult.TimePeriod, + checkResult.SubmitCount, + checkResult.MaxCount, + ) + if checkResult.SubmitCount+1 > checkResult.MaxCount { + err = fmt.Errorf("submit count exceeded") + } + } + 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 main() { + 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) + confObj, _, err := conf.ParseConfFile(confPath) + if err != nil { + slog.Error("parse conf", "error", err) + return + } + checkResults, err := check(confObj) + if err != nil { + slog.Error("teapot check", "error", err) + return + } + exitCode := 0 + output, err := generateOutput(checkResults) + if err != nil { + exitCode = 1 + } + fmt.Println(output) + os.Exit(exitCode) +}