Introduce remote-challenge-sync-airbus

This commit is contained in:
nemunaire 2022-06-06 12:55:39 +02:00
parent cf502bd9d5
commit bfdb1c2bf7
6 changed files with 309 additions and 21 deletions

View File

@ -256,6 +256,21 @@ steps:
branch:
- master
- name: docker remote-challenge-sync-airbus
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: nemunaire/fic-remote-challenge-sync-airbus
auto_tag: true
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
dockerfile: Dockerfile-remote-challenge-sync-airbus
when:
branch:
- master
trigger:
event:
- cron
@ -557,21 +572,6 @@ steps:
password:
from_secret: docker_password
- name: docker remote-scores-sync-zqds
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: nemunaire/fic-remote-scores-sync-zqds
auto_tag: true
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
dockerfile: Dockerfile-remote-scores-sync-zqds
when:
branch:
- master
trigger:
event:
- push

View File

@ -0,0 +1,24 @@
FROM golang:1-alpine as gobuild
RUN apk add --no-cache git
WORKDIR /go/src/srs.epita.fr/fic-server/
COPY go.mod go.sum ./
COPY libfic ./libfic/
COPY settings ./settings/
COPY remote/challenge-sync-airbus ./remote/challenge-sync-airbus/
RUN go get -d -v ./remote/challenge-sync-airbus && \
go build -v -buildvcs=false -o ./challenge-sync-airbus ./remote/challenge-sync-airbus
FROM alpine:3.16
RUN apk add --no-cache openssl ca-certificates
WORKDIR /srv
ENTRYPOINT ["/srv/challenge-sync-airbus"]
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/challenge-sync-airbus /srv/challenge-sync-airbus

View File

@ -192,13 +192,12 @@ services:
- /var/lib/fic/startingblock
- /var/lib/fic/submissions
- /var/lib/fic/teams
- name: fic-remote-scores-sync-zqds
image: nemunaire/fic-remote-scores-sync-zqds:latest
- name: fic-remote-challenge-sync-airbus
image: nemunaire/fic-remote-challenge-sync-airbus:latest
env:
- ZQDS_EVENTID=6109ae5acbb7b36b789c9330
- ZQDS_ROUNDID=612d3a5179fe4f747ea89274
- ZQDS_CLIENTID=
- ZQDS_CLIENTSECRET=
- AIRBUS_BASEURL=https://....
- AIRBUS_TOKEN=abcdef0123456789abcdef0123456789
- AIRBUS_SESSIONID=42
binds:
- /etc/hosts:/etc/hosts:ro
- /var/lib/fic/teams:/srv/TEAMS:ro

View File

@ -0,0 +1 @@
challenge-sync-airbus

View File

@ -0,0 +1,166 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
)
type AirbusAPI struct {
BaseURL string
Token string
SessionID uint64
}
func (a *AirbusAPI) request(method, endpoint string, data []byte, out interface{}) error {
var reader *bytes.Reader
if data != nil {
reader = bytes.NewReader(data)
}
req, err := http.NewRequest(method, a.BaseURL+endpoint, reader)
if err != nil {
return fmt.Errorf("unable to prepare request to %q: %w", endpoint, err)
}
req.Header.Add("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("error during request execution to %q: %w", endpoint, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if out != nil {
jdec := json.NewDecoder(resp.Body)
if err := jdec.Decode(out); err != nil {
return fmt.Errorf("an error occurs when trying to decode response: %w", err)
}
}
} else if all, err := io.ReadAll(resp.Body); err != nil {
return fmt.Errorf("error returned by the API + error on decoding: %d // %w", resp.StatusCode, err)
} else {
return fmt.Errorf("error returned by the API: %d -> %s", resp.StatusCode, all)
}
return nil
}
type AirbusUserId int64
func (aui AirbusUserId) String() string {
return strconv.FormatInt(int64(aui), 10)
}
type AirbusUser struct {
Id AirbusUserId `json:"id"`
Name string `json:"name"`
}
func (a *AirbusAPI) GetUsers() (users []AirbusUser, err error) {
err = a.request("GET", fmt.Sprintf("/sessions/%d/users", a.SessionID), nil, &users)
return
}
func (a *AirbusAPI) GetUserFromName(name string) (*AirbusUser, error) {
users, err := a.GetUsers()
if err != nil {
return nil, fmt.Errorf("unable to retrieve users list: %w", err)
}
for _, u := range users {
if u.Name == name {
return &u, nil
}
}
return nil, fmt.Errorf("unable to find user %q", name)
}
type AirbusChallengeId int64
func (aci AirbusChallengeId) String() string {
return strconv.FormatInt(int64(aci), 10)
}
type AirbusChallenge struct {
Id AirbusChallengeId `json:"id"`
Name string `json:"name"`
}
func (a *AirbusAPI) GetChallenges() (challenges []AirbusChallenge, err error) {
err = a.request("GET", fmt.Sprintf("/sessions/%d/challenges", a.SessionID), nil, &challenges)
return
}
func (a *AirbusAPI) GetChallengeFromName(name string) (*AirbusChallenge, error) {
challenges, err := a.GetChallenges()
if err != nil {
return nil, fmt.Errorf("unable to retrieve challenges list: %w", err)
}
for _, c := range challenges {
if c.Name == name {
return &c, nil
}
}
return nil, fmt.Errorf("unable to find challenge %q", name)
}
func (a *AirbusAPI) ValidateChallengeFromUser(user *AirbusUser, challenge *AirbusChallenge) (err error) {
err = a.request("GET", fmt.Sprintf("/sessions/%d/%s/%s/validate", a.SessionID, challenge.Id.String(), user.Id.String()), nil, nil)
return
}
type AirbusUserAwards struct {
UserId AirbusUserId `json:"gaming_user_id"`
Message string `json:"name"`
Value int64 `json:"value"`
}
func (a *AirbusAPI) AwardUser(user *AirbusUser, value int64, message string) (err error) {
awards := AirbusUserAwards{
UserId: user.Id,
Message: message,
Value: value,
}
j, err := json.Marshal(awards)
if err != nil {
return fmt.Errorf("unable to marshall JSON from awards struct: %w", err)
}
err = a.request("POST", fmt.Sprintf("/sessions/%d/awards", a.SessionID), j, nil)
return
}
type AirbusStats struct {
Data AirbusStatsData `json:"data"`
}
type AirbusStatsData struct {
BySessions []AirbusStatsSession `json:"by_sessions"`
}
type AirbusStatsSession struct {
UUID AirbusUUID `json:"uuid"`
Name string `json:"name"`
Duration string `json:"duration"`
TeamStats []AirbusTeamStats `json:"team_stats"`
}
type AirbusTeamStats struct {
UUID AirbusUUID `json:"uuid"`
Name string `json:"name"`
Score int64 `json:"score"`
}
func (a *AirbusAPI) GetCurrentStats() (stats AirbusStats, err error) {
err = a.request("GET", "/stats", nil, &stats)
return
}

View File

@ -0,0 +1,98 @@
package main
import (
"flag"
"log"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"gopkg.in/fsnotify.v1"
)
var (
TeamsDir string
skipInitialSync bool
)
func main() {
flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events")
flag.BoolVar(&skipInitialSync, "skipinitialsync", skipInitialSync, "Skip the initial synchronization")
flag.Parse()
api := AirbusAPI{
BaseURL: "https://portal.european-cybercup.lan/api/v1",
}
if v, exists := os.LookupEnv("AIRBUS_BASEURL"); exists {
api.BaseURL = v
}
if v, exists := os.LookupEnv("AIRBUS_TOKEN"); exists {
api.Token = v
}
if v, exists := os.LookupEnv("AIRBUS_SESSIONID"); exists {
var err error
api.SessionID, err = strconv.ParseUint(v, 10, 64)
if err != nil {
log.Fatal("AIRBUS_SESSIONID is invalid: ", err.Error())
}
}
log.SetPrefix("[challenge-sync-airbus] ")
TeamsDir = path.Clean(TeamsDir)
log.Println("Registering directory events...")
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
if err := watcher.Add(TeamsDir); err != nil {
log.Fatal(err)
}
if !skipInitialSync {
if _, err := os.Stat(path.Join(TeamsDir, "teams.json")); err == nil {
treatAll(path.Join(TeamsDir, "teams.json"))
}
}
// Register SIGUSR1, SIGUSR2
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, syscall.SIGHUP)
watchedNotify := fsnotify.Create
for {
select {
case <-interrupt:
log.Println("SIGHUP received, resyncing all teams' score...")
treatAll(path.Join(TeamsDir, "teams.json"))
log.Println("SIGHUP treated.")
case ev := <-watcher.Events:
if path.Base(ev.Name) == "teams.json" {
if ev.Op&watchedNotify == watchedNotify {
if *debugINotify {
log.Println("Treating event:", ev, "for", ev.Name)
}
go treatDiff(ev.Name)
} else if ev.Op&fsnotify.Write == fsnotify.Write {
log.Println("FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved.")
watchedNotify = fsnotify.Write
go treatDiff(ev.Name)
} else if *debugINotify {
log.Println("Skipped teams.json event:", ev)
}
} else if *debugINotify {
log.Println("Skipped NON teams.json event:", ev, "for", ev.Name)
}
case err := <-watcher.Errors:
log.Println("error:", err)
}
}
}