2022-06-06 10:55:39 +00:00
package main
import (
"flag"
2024-03-19 10:49:39 +00:00
"fmt"
2022-06-07 15:04:07 +00:00
"io/ioutil"
2022-06-06 10:55:39 +00:00
"log"
"os"
2022-06-07 15:13:30 +00:00
"os/signal"
2022-06-06 10:55:39 +00:00
"path"
2022-06-07 14:06:36 +00:00
"path/filepath"
2024-03-19 10:49:39 +00:00
"sort"
2022-06-06 10:55:39 +00:00
"strconv"
2024-03-19 10:49:39 +00:00
"strings"
2022-06-07 15:13:30 +00:00
"syscall"
2022-06-07 22:09:49 +00:00
"time"
2022-06-07 15:04:07 +00:00
"gopkg.in/fsnotify.v1"
2022-06-06 10:55:39 +00:00
)
var (
TeamsDir string
skipInitialSync bool
2023-04-06 01:48:52 +00:00
dryRun bool
2022-06-06 10:55:39 +00:00
)
func main ( ) {
flag . StringVar ( & TeamsDir , "teams" , "./TEAMS" , "Base directory where save teams JSON files" )
2022-06-07 15:04:07 +00:00
var debugINotify = flag . Bool ( "debuginotify" , false , "Show skipped inotofy events" )
2023-04-06 01:48:52 +00:00
flag . BoolVar ( & dryRun , "dry-run" , dryRun , "Don't perform any write action, just display" )
2022-06-06 10:55:39 +00:00
flag . BoolVar ( & skipInitialSync , "skipinitialsync" , skipInitialSync , "Skip the initial synchronization" )
2023-04-03 10:04:48 +00:00
flag . BoolVar ( & noValidateChallenge , "no-validate-challenge" , noValidateChallenge , "Consider challenge validation as a standard award (if each exercice hasn't been imported on their side)" )
2024-03-19 10:49:39 +00:00
skipVerify := flag . Bool ( "skip-tls-verify" , false , "Allow not verified certificates (INSECURE!)" )
tsFromFile := flag . Bool ( "timestamp-from-file" , false , "Load timestamp matching for score from filesystem instead of the API" )
2022-06-07 15:04:07 +00:00
daemon := flag . Bool ( "watch" , false , "Enable daemon mode by watching the directory" )
2022-06-07 14:06:36 +00:00
tspath := flag . String ( "timestamp-file" , "./REMOTE/timestamp" , "Path to the file storing the last timestamp" )
exercicespath := flag . String ( "exercices-file" , "./REMOTE/exercices-bindings.json" , "Path to the file containing the ID bindings" )
coeff := flag . Float64 ( "global-coeff" , 10.0 , "Coefficient to use to multiply all scores before passing them to the other platform" )
2022-06-06 10:55:39 +00:00
flag . Parse ( )
api := AirbusAPI {
2024-03-19 10:49:39 +00:00
BaseURL : "https://portal.european-cybercup.lan/api" ,
InsecureSkipVerify : * skipVerify ,
2022-06-06 10:55:39 +00:00
}
if v , exists := os . LookupEnv ( "AIRBUS_BASEURL" ) ; exists {
api . BaseURL = v
2024-03-22 18:08:21 +00:00
} else if v , exists := os . LookupEnv ( "AIRBUS_BASEURL_FILE" ) ; exists {
fd , err := os . Open ( v )
if err != nil {
log . Fatal ( "Unable to open AIRBUS_BASEURL_FILE:" , err )
}
b , _ := ioutil . ReadAll ( fd )
api . BaseURL = strings . TrimSpace ( string ( b ) )
fd . Close ( )
2022-06-06 10:55:39 +00:00
}
2024-03-19 10:49:39 +00:00
if v , exists := os . LookupEnv ( "AIRBUS_SKIP_TLS_VERIFY" ) ; exists {
var err error
api . InsecureSkipVerify , err = strconv . ParseBool ( v )
if err != nil {
log . Fatal ( "Unable to parse boolean value in AIRBUS_SKIP_TLS_VERIFY:" , err )
}
}
2022-06-06 10:55:39 +00:00
if v , exists := os . LookupEnv ( "AIRBUS_TOKEN" ) ; exists {
api . Token = v
2024-03-22 18:08:21 +00:00
} else if v , exists := os . LookupEnv ( "AIRBUS_TOKEN_FILE" ) ; exists {
fd , err := os . Open ( v )
if err != nil {
log . Fatal ( "Unable to open AIRBUS_TOKEN_FILE:" , err )
}
b , _ := ioutil . ReadAll ( fd )
api . Token = strings . TrimSpace ( string ( b ) )
fd . Close ( )
2022-06-06 10:55:39 +00:00
}
2023-04-06 01:48:52 +00:00
2022-06-06 10:55:39 +00:00
if v , exists := os . LookupEnv ( "AIRBUS_SESSIONID" ) ; exists {
var err error
2022-06-07 14:06:36 +00:00
api . SessionID , err = strconv . ParseInt ( v , 10 , 64 )
2022-06-06 10:55:39 +00:00
if err != nil {
log . Fatal ( "AIRBUS_SESSIONID is invalid: " , err . Error ( ) )
}
2024-03-22 18:08:21 +00:00
} else if v , exists := os . LookupEnv ( "AIRBUS_SESSION_NAME_FILE" ) ; exists {
fd , err := os . Open ( v )
if err != nil {
log . Fatal ( "Unable to open AIRBUS_SESSION_NAME_FILE:" , err )
}
b , _ := ioutil . ReadAll ( fd )
fd . Close ( )
v = strings . TrimSpace ( string ( b ) )
sessions , err := api . GetSessions ( )
if err != nil {
log . Fatal ( "Unable to retrieve session: " , err )
}
for _ , session := range sessions {
if session . Name == v {
api . SessionID = session . ID
break
}
}
if api . SessionID == 0 {
log . Fatal ( "Session ID not found" )
} else {
log . Println ( "Session ID discovered: " , api . SessionID )
}
2023-04-06 01:48:52 +00:00
} else if v , exists := os . LookupEnv ( "AIRBUS_SESSION_NAME" ) ; exists {
sessions , err := api . GetSessions ( )
if err != nil {
log . Fatal ( "Unable to retrieve session: " , err )
}
for _ , session := range sessions {
if session . Name == v {
api . SessionID = session . ID
break
}
}
2023-04-06 13:37:05 +00:00
if api . SessionID == 0 {
log . Fatal ( "Session ID not found" )
} else {
log . Println ( "Session ID discovered: " , api . SessionID )
}
2024-03-19 10:49:39 +00:00
} else {
sessions , err := api . GetSessions ( )
if err != nil {
log . Fatal ( "Unable to retrieve session (check your credentials!): " , err )
}
2023-04-06 01:48:52 +00:00
2024-03-19 10:49:39 +00:00
log . Println ( "Please define your AIRBUS_SESSIONID or AIRBUS_SESSION_NAME." )
log . Println ( "Existing sessions are:" )
for _ , session := range sessions {
log . Printf ( " - %d: %q" , session . ID , session . Name )
}
os . Exit ( 1 )
2022-06-07 22:09:49 +00:00
}
2022-06-06 10:55:39 +00:00
log . SetPrefix ( "[challenge-sync-airbus] " )
2024-03-19 10:49:39 +00:00
if flag . NArg ( ) > 0 {
args := flag . Args ( )
switch args [ 0 ] {
case "list" :
teams , err := api . GetTeams ( )
if err != nil {
log . Println ( "Unable to retrieve teams:" , err )
os . Exit ( 1 )
}
fmt . Println ( "## Airbus' registered teams:" )
fmt . Println ( "-------------------------------------------" )
fmt . Println ( " ID | Name | Nb. | Score | Rank" )
fmt . Println ( "-------------------------------------------" )
for _ , team := range teams {
fmt . Printf ( "% 2d | % 15s | % 3d | % 5d | % 3d\n" , team . ID , team . Name , len ( team . Members ) , team . Score , team . Rank )
}
case "rank" :
teams , err := api . GetTeams ( )
if err != nil {
log . Println ( "Unable to retrieve teams:" , err )
os . Exit ( 1 )
}
ranking := [ ] * AirbusTeam { }
for _ , team := range teams {
tmp := team
ranking = append ( ranking , & tmp )
}
sort . Sort ( sort . Reverse ( ByScore ( ranking ) ) )
fmt . Println ( "## Airbus' ranking:" )
fmt . Println ( "-------------------------------------" )
fmt . Println ( " Rank | Name | ID | Score" )
fmt . Println ( "-------------------------------------" )
for _ , team := range ranking {
fmt . Printf ( "% 5d | % 15s |% 3d | % 5d\n" , team . Rank , team . Name , team . ID , team . Score )
}
case "get" :
teams , err := api . GetTeams ( )
if err != nil {
log . Println ( "Unable to retrieve teams:" , err )
os . Exit ( 1 )
}
teamid , err := strconv . ParseInt ( args [ 1 ] , 10 , 64 )
if err != nil {
log . Println ( err )
os . Exit ( 1 )
}
for _ , team := range teams {
if team . ID == teamid {
fmt . Printf ( "## Airbus' registered team %d:\n\nID: %d\nName: %s\nScore: %d\nRank: %d\nMembers:\n" , teamid , team . ID , team . Name , team . Score , team . Rank )
for _ , member := range team . Members {
fmt . Printf ( " - ID: %d\n Name: %s\n Nickname: %s\n E-mail: %s\n" , member . ID , member . Name , member . Nickname , member . EMail )
}
os . Exit ( 0 )
}
}
fmt . Printf ( "Team %d not found. Use 'list' to view all existing teams\n" , teamid )
case "award" :
if len ( args ) < 3 {
fmt . Println ( "award <TEAM_ID> <VALUE> <MESSAGE>" )
os . Exit ( 1 )
}
teams , err := api . GetTeams ( )
if err != nil {
log . Println ( "Unable to retrieve teams:" , err )
os . Exit ( 1 )
}
teamid , err := strconv . ParseInt ( args [ 1 ] , 10 , 64 )
if err != nil {
log . Println ( "Invalid team id" , err )
os . Exit ( 1 )
}
value , err := strconv . ParseInt ( args [ 2 ] , 10 , 64 )
if err != nil {
log . Println ( "Invalid award value:" , err )
os . Exit ( 1 )
}
for _ , team := range teams {
if team . ID == teamid {
err = api . AwardUser ( & team , value , strings . Join ( args [ 3 : ] , " " ) )
if err != nil {
log . Println ( "Unable to award team:" , err )
os . Exit ( 1 )
}
fmt . Println ( "Team awarded" )
os . Exit ( 0 )
}
}
fmt . Printf ( "Team %d not found. Use 'list' to view all existing teams\n" , teamid )
}
os . Exit ( 0 )
}
var err error
2022-06-06 10:55:39 +00:00
TeamsDir = path . Clean ( TeamsDir )
2024-03-19 10:49:39 +00:00
w := Walker {
API : api ,
Coeff : * coeff ,
2022-06-06 10:55:39 +00:00
}
2022-06-07 14:06:36 +00:00
// Load teams.json
2024-03-19 10:49:39 +00:00
w . Teams , err = getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
2022-06-07 14:06:36 +00:00
if err != nil {
log . Fatal ( "Unable to open teams bindings file: " , err . Error ( ) )
}
2024-03-19 10:49:39 +00:00
log . Println ( "Team bindings loaded: " , len ( w . Teams ) )
2022-06-07 14:06:36 +00:00
2024-03-19 10:49:39 +00:00
// Fetch teams from Airbus API
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
log . Fatal ( "Unable to fetch Airbus teams: " , err . Error ( ) )
}
2024-03-19 10:49:39 +00:00
// Load the timestamp
if * tsFromFile {
w . LastSync , err = loadTS ( * tspath )
} else {
w . LastSync , err = loadTSFromAPI ( w . TeamBindings )
}
if err != nil {
log . Fatal ( "Unable to open timestamp file: " , err . Error ( ) )
}
2023-04-06 01:48:52 +00:00
if ! noValidateChallenge {
// Load exercices bindings
w . Exercices , err = ReadExercicesBindings ( * exercicespath )
if err != nil {
log . Fatal ( "Unable to open exercices bindings file: " , err . Error ( ) )
}
2022-06-06 10:55:39 +00:00
}
2022-06-07 15:04:07 +00:00
if ! skipInitialSync {
2024-03-19 10:49:39 +00:00
log . Println ( "Doing initial score balance" )
2023-04-06 13:37:05 +00:00
err = w . BalanceScores ( )
2022-06-07 15:04:07 +00:00
if err != nil {
2023-04-06 13:37:05 +00:00
log . Println ( "Something goes wrong during score balance: " , err . Error ( ) )
2022-06-07 15:04:07 +00:00
}
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
2022-06-06 10:55:39 +00:00
2024-03-19 10:49:39 +00:00
log . Println ( "initial sync done" )
}
2023-04-06 13:37:05 +00:00
2022-06-07 15:04:07 +00:00
if daemon != nil && * daemon {
// Watch teams.json and scores.json
log . Println ( "Registering directory events..." )
watcher , err := fsnotify . NewWatcher ( )
if err != nil {
log . Fatal ( err )
}
defer watcher . Close ( )
2022-06-06 10:55:39 +00:00
2022-06-07 15:04:07 +00:00
if err := watchsubdir ( watcher , TeamsDir ) ; err != nil {
log . Fatal ( err )
}
2022-06-07 15:13:30 +00:00
// Register SIGUSR1, SIGUSR2
interrupt1 := make ( chan os . Signal , 1 )
signal . Notify ( interrupt1 , syscall . SIGHUP )
interrupt2 := make ( chan os . Signal , 1 )
signal . Notify ( interrupt2 , syscall . SIGUSR1 )
interrupt3 := make ( chan os . Signal , 1 )
signal . Notify ( interrupt3 , syscall . SIGUSR2 )
2024-03-19 10:49:39 +00:00
var ticker * time . Ticker
if * tsFromFile {
ticker = time . NewTicker ( 5 * time . Second )
} else {
ticker = time . NewTicker ( 5 * time . Minute )
}
2022-06-07 15:13:30 +00:00
2022-06-07 15:04:07 +00:00
watchedNotify := fsnotify . Create
for {
select {
2022-06-07 15:13:30 +00:00
case <- interrupt1 :
log . Println ( "SIGHUP received, reloading files..." )
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Println ( "Unable to open teams bindings file: " , err . Error ( ) )
2024-03-19 10:49:39 +00:00
} else {
w . Teams = teamsbindings
2022-06-07 15:13:30 +00:00
}
2024-03-19 10:49:39 +00:00
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
2024-03-19 10:49:39 +00:00
log . Println ( "Unable to fetch teams: " , err . Error ( ) )
}
if ! * tsFromFile {
ts , err := loadTSFromAPI ( w . TeamBindings )
if err != nil {
log . Println ( "Unable to refresh timestamp: " , err . Error ( ) )
} else {
w . LastSync = ts
}
2023-04-06 01:48:52 +00:00
}
2022-06-07 15:13:30 +00:00
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
2024-03-19 10:49:39 +00:00
log . Println ( "Unable to save timestamp file: " , err . Error ( ) )
2022-06-07 15:13:30 +00:00
}
log . Println ( "SIGHUP treated." )
case <- interrupt2 :
log . Println ( "SIGUSR1 received, resynching all teams" )
// Iterate over teams scores
2022-06-07 22:09:49 +00:00
err = filepath . WalkDir ( TeamsDir , w . WalkScoreSync )
2022-06-07 15:13:30 +00:00
if err != nil {
log . Printf ( "Something goes wrong during walking" )
}
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
2024-03-19 10:49:39 +00:00
log . Println ( "Unable to save timestamp file: " , err . Error ( ) )
2022-06-07 15:13:30 +00:00
}
log . Println ( "SIGUSR1 treated." )
case <- interrupt3 :
log . Println ( "SIGUSR2 received, resynching all teams from teams.json" )
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Println ( "Unable to open teams bindings file: " , err . Error ( ) )
return
2024-03-19 10:49:39 +00:00
} else {
w . Teams = teamsbindings
2022-06-07 15:13:30 +00:00
}
2024-03-19 10:49:39 +00:00
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
2024-03-19 10:49:39 +00:00
log . Println ( "Unable to fetch teams: " , err . Error ( ) )
return
2023-04-06 01:48:52 +00:00
}
2022-06-07 15:13:30 +00:00
2024-03-19 10:49:39 +00:00
if ! * tsFromFile {
ts , err := loadTSFromAPI ( w . TeamBindings )
if err != nil {
log . Println ( "Unable to refresh timestamp: " , err . Error ( ) )
return
} else {
w . LastSync = ts
}
}
err = w . BalanceScores ( )
if err != nil {
log . Println ( "Unable to balance scores: " , err . Error ( ) )
}
2022-06-07 15:13:30 +00:00
log . Println ( "SIGUSR2 treated." )
2022-06-07 15:04:07 +00:00
case ev := <- watcher . Events :
2024-03-19 10:49:39 +00:00
d , err := os . Lstat ( ev . Name )
2024-03-27 20:27:24 +00:00
if err == nil && strings . HasPrefix ( d . Name ( ) , "." ) {
if * debugINotify {
log . Println ( "Skipped event:" , ev , "for" , ev . Name )
}
continue
}
2024-03-19 10:49:39 +00:00
if err == nil && watchedNotify == fsnotify . Create && 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
}
2024-03-27 20:27:24 +00:00
if err == nil && ev . Op & fsnotify . Create == fsnotify . Create && d . Mode ( ) . IsDir ( ) && d . Mode ( ) & os . ModeSymlink == 0 {
2022-06-07 15:04:07 +00:00
// Register new subdirectory
if err := watchsubdir ( watcher , ev . Name ) ; err != nil {
log . Println ( err )
}
2023-04-06 13:37:05 +00:00
} else if err == nil && ev . Op & watchedNotify == watchedNotify && d . Mode ( ) . IsRegular ( ) {
2022-06-07 15:04:07 +00:00
if * debugINotify {
log . Println ( "Treating event:" , ev , "for" , ev . Name )
}
2024-03-19 10:49:39 +00:00
if watchedNotify == fsnotify . Write {
time . Sleep ( 100 * time . Millisecond )
}
2022-06-07 15:04:07 +00:00
if filepath . Base ( ev . Name ) == "scores.json" {
go w . treat ( ev . Name )
} else if filepath . Base ( ev . Name ) == "teams.json" {
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Println ( "Unable to open teams bindings file: " , err . Error ( ) )
2024-03-19 10:49:39 +00:00
} else {
w . Teams = teamsbindings
2022-06-07 15:04:07 +00:00
}
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
2024-03-19 10:49:39 +00:00
log . Println ( "Unable to fetch teams: " , err . Error ( ) )
2023-04-06 01:48:52 +00:00
}
2022-06-07 15:04:07 +00:00
}
2023-04-06 13:37:05 +00:00
} else if err == nil && * debugINotify {
2022-06-07 15:04:07 +00:00
log . Println ( "Skipped event:" , ev , "for" , ev . Name )
}
case err := <- watcher . Errors :
log . Println ( "error:" , err )
2022-06-07 15:13:30 +00:00
case <- ticker . C :
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
2024-03-19 10:49:39 +00:00
log . Println ( "Unable to save timestamp file: " , err . Error ( ) )
2022-06-07 15:13:30 +00:00
}
2022-06-07 15:04:07 +00:00
}
}
}
2022-06-07 15:13:30 +00:00
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
2022-06-07 15:04:07 +00:00
}
func watchsubdir ( watcher * fsnotify . Watcher , pathname string ) error {
log . Println ( "Watch new directory:" , pathname )
if err := watcher . Add ( pathname ) ; err != nil {
return err
}
if ds , err := ioutil . ReadDir ( pathname ) ; err != nil {
return err
} else {
for _ , d := range ds {
p := path . Join ( pathname , d . Name ( ) )
2024-03-27 20:27:24 +00:00
if d . IsDir ( ) && ! strings . HasPrefix ( d . Name ( ) , "." ) && d . Mode ( ) & os . ModeSymlink == 0 {
2022-06-07 15:04:07 +00:00
if err := watchsubdir ( watcher , p ) ; err != nil {
return err
}
}
}
return nil
2022-06-06 10:55:39 +00:00
}
}