feat(healthcheck): add teapot-checker to repo-health-checker
This commit is contained in:
parent
648fe7d0a4
commit
0f5ccf8af2
|
@ -1,11 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/conf"
|
||||||
"github.com/joint-online-judge/JOJ3/pkg/healthcheck"
|
"github.com/joint-online-judge/JOJ3/pkg/healthcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +46,7 @@ var (
|
||||||
checkFileSumList string
|
checkFileSumList string
|
||||||
metaFile []string
|
metaFile []string
|
||||||
gitWhitelist []string
|
gitWhitelist []string
|
||||||
|
confPath string
|
||||||
showVersion *bool
|
showVersion *bool
|
||||||
Version string
|
Version string
|
||||||
)
|
)
|
||||||
|
@ -58,9 +62,42 @@ func init() {
|
||||||
parseMultiValueFlag(&metaFile, "meta", "meta files to check")
|
parseMultiValueFlag(&metaFile, "meta", "meta files to check")
|
||||||
// TODO: remove gitWhitelist, it is only for backward compatibility now
|
// TODO: remove gitWhitelist, it is only for backward compatibility now
|
||||||
parseMultiValueFlag(&gitWhitelist, "whitelist", "[DEPRECATED] will be ignored")
|
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() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
|
@ -77,28 +114,19 @@ func main() {
|
||||||
"meta", metaFile,
|
"meta", metaFile,
|
||||||
)
|
)
|
||||||
var err error
|
var err error
|
||||||
err = healthcheck.RepoSize(repoSize)
|
confObj, groups, actor, repoName, err := prepareTeapotCheck()
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("### Forbidden File Check Failed:\n%s\n", err.Error())
|
slog.Error("marshal result", "error", err)
|
||||||
}
|
os.Exit(1)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
fmt.Println(string(jsonRes))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package healthcheck
|
package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/criyle/go-judge/envexec"
|
"github.com/criyle/go-judge/envexec"
|
||||||
"github.com/joint-online-judge/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
"github.com/joint-online-judge/JOJ3/pkg/healthcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Healthcheck struct{}
|
type Healthcheck struct{}
|
||||||
|
@ -21,15 +23,28 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) (stage.ParserResult,
|
||||||
return stage.ParserResult{
|
return stage.ParserResult{
|
||||||
Score: 0,
|
Score: 0,
|
||||||
Comment: fmt.Sprintf(
|
Comment: fmt.Sprintf(
|
||||||
"Unexpected executor status: `%s`\n`stdout`:\n```%s\n```\n`stderr`:\n```%s\n```",
|
"Unexpected executor status: `%s`\n`stderr`:\n```%s\n```\n",
|
||||||
executorResult.Status, stdout, stderr,
|
executorResult.Status, stderr,
|
||||||
),
|
),
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
var res healthcheck.Result
|
||||||
|
err := json.Unmarshal([]byte(stdout), &res)
|
||||||
|
if err != nil {
|
||||||
return stage.ParserResult{
|
return stage.ParserResult{
|
||||||
Score: 0,
|
Score: 0,
|
||||||
Comment: stdout,
|
Comment: fmt.Sprintf(
|
||||||
}, stdout != ""
|
"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: comment,
|
||||||
|
}, forceQuit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
|
func (*Healthcheck) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
|
|
61
pkg/healthcheck/all.go
Normal file
61
pkg/healthcheck/all.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,16 +1,14 @@
|
||||||
package main
|
package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/joint-online-judge/JOJ3/cmd/joj3/env"
|
|
||||||
"github.com/joint-online-judge/JOJ3/internal/conf"
|
"github.com/joint-online-judge/JOJ3/internal/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,20 +19,9 @@ type CheckResult struct {
|
||||||
TimePeriod int `json:"time_period"`
|
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("LOG_FILE_PATH", conf.Teapot.LogPath)
|
||||||
os.Setenv("_TYPER_STANDARD_TRACEBACK", "1")
|
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
|
var formattedGroups []string
|
||||||
for _, group := range conf.Teapot.Groups {
|
for _, group := range conf.Teapot.Groups {
|
||||||
groupConfig := fmt.Sprintf("%s=%d:%d",
|
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)
|
slog.Error("teapot check exec", "error", err)
|
||||||
return
|
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,
|
slog.Error("unmarshal teapot check result", "error", err,
|
||||||
"stdout", stdoutBuf.String())
|
"stdout", stdoutBuf.String())
|
||||||
return
|
return
|
||||||
|
@ -86,73 +73,35 @@ func generateOutput(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
comment += fmt.Sprintf(
|
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.TimePeriod,
|
||||||
checkResult.SubmitCount,
|
checkResult.SubmitCount,
|
||||||
checkResult.MaxCount,
|
checkResult.MaxCount,
|
||||||
)
|
)
|
||||||
if useGroup && checkResult.SubmitCount+1 > 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSlog() {
|
func TeapotCheck(
|
||||||
opts := &slog.HandlerOptions{}
|
conf *conf.Conf, actor, repoName string, groups []string,
|
||||||
handler := slog.NewTextHandler(os.Stderr, opts)
|
) (output string, err error) {
|
||||||
logger := slog.New(handler)
|
checkResults, err := runTeapot(conf, actor, repoName)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("teapot check", "error", err)
|
slog.Error("teapot check", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
output, err := generateOutput(checkResults, groups)
|
output, err = generateOutput(checkResults, groups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("generate output", "error", err)
|
slog.Error("generate output", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println(output)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := mainImpl(); err != nil {
|
|
||||||
slog.Error("main exit", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user