diff --git a/README.md b/README.md index 8af3759..393eb08 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ # ubcctf/instanced +currently Jank As Hell. + Manages challenge instances on-demand. `instanced` runs in the cluster and exposes an HTTP API which is used to request instances. +Challenge templates are added in the form of CRDs. Example format is in this repository. +`instanced` must be restarted every time new CRDs are applied. + +Instances created are kept track of in a local sqlite database. The instancer periodically scans the database for expired instances and deletes them. + - GET `/instances` - get list of active instances +- GET `/challenges?team=$ID` - get list of available challenges and instance states for specific team - POST `/instances?chal=$CHALLNAME&team=$ID` - provision an instance for specific challenge and team - DELETE `/instances?id=$ID` - delete challenge with id -Authenticate with `Bearer token` diff --git a/db.go b/db.go index 9c73d44..c18f79a 100644 --- a/db.go +++ b/db.go @@ -109,3 +109,33 @@ func (in *Instancer) ReadInstanceRecords() ([]InstanceRecord, error) { err = rows.Err() return records, err } + +func (in *Instancer) ReadInstanceRecordsTeam(teamID string) ([]InstanceRecord, error) { + if in.db == nil { + return nil, errors.New("db not initialized") + } + stmt, err := in.db.Prepare("SELECT id, challenge, team, expiry FROM instances WHERE team = ?") + if err != nil { + return nil, err + } + defer stmt.Close() + + rows, err := stmt.Query(teamID) + if err != nil { + return nil, err + } + defer rows.Close() + records := make([]InstanceRecord, 0) + for rows.Next() { + record := InstanceRecord{} + var t int64 + err = rows.Scan(&record.Id, &record.Challenge, &record.TeamID, &t) + if err != nil { + return records, err + } + record.Expiry = time.Unix(t, 0) + records = append(records, record) + } + err = rows.Err() + return records, err +} diff --git a/instancer.go b/instancer.go index 2e2b8a5..9a8543e 100644 --- a/instancer.go +++ b/instancer.go @@ -171,6 +171,26 @@ func (in *Instancer) CreateInstance(challenge, team string) (InstanceRecord, err return rec, nil } +func (in *Instancer) GetTeamChallengeStates(teamID string) ([]InstanceRecord, error) { + instances, err := in.ReadInstanceRecordsTeam(teamID) + if err != nil { + return nil, err + } + for k := range in.challengeObjs { + active := false + for _, v := range instances { + if v.Challenge == k { + active = true + break + } + } + if !active { + instances = append(instances, InstanceRecord{Expiry: time.Unix(0, 0), Challenge: k, TeamID: teamID}) + } + } + return instances, nil +} + func (in *Instancer) Start() error { log := in.log.With().Str("component", "instanced").Logger() log.Info().Msg("starting webserver...") diff --git a/webserver.go b/webserver.go index ba19708..1cdc7fc 100644 --- a/webserver.go +++ b/webserver.go @@ -17,6 +17,8 @@ func (in *Instancer) registerEndpoints() { in.echo.POST("/instances", in.handleInstanceCreate) in.echo.DELETE("/instances", in.handleInstanceDelete) + + in.echo.GET("/challenges", in.handleInstanceListTeam) } type InstancesResponse struct { @@ -82,3 +84,14 @@ func (in *Instancer) handleInstanceList(c echo.Context) error { // todo: properly marshal records return c.JSON(http.StatusOK, records) } + +func (in *Instancer) handleInstanceListTeam(c echo.Context) error { + teamID := c.QueryParam("team") + records, err := in.GetTeamChallengeStates(teamID) + if err != nil { + c.Logger().Errorf("request failed: %v", err) + return c.JSON(http.StatusInternalServerError, "request failed") + } + // todo: properly marshal records + return c.JSON(http.StatusOK, records) +}