Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ IMAGE_TAG ?= latest
PLATFORMS ?= linux/amd64
DOCKER_COMMAND ?= $(notdir $(shell command -v docker || command -v podman))
DOCKER_BUILD_COMMAND ?= buildx build
BUILDX_BUILDER ?= hobbyfarm
SERVICE_BASE_PATH ?= $(shell realpath ./v3/services)
SERVICE_DOCKERFILE ?= $(shell realpath ./v3/Dockerfile)
GARGANTUA_DOCKERFILE ?= $(shell realpath ./Dockerfile)
Expand All @@ -26,7 +27,7 @@ endif

# If no services are specified, get all service directories
ifeq ($(SERVICES),)
SERVICES := gargantua $(shell find $(SERVICE_BASE_PATH) -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
SERVICES := gargantua $(shell find "$(SERVICE_BASE_PATH)" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
endif

.PHONY: help
Expand Down Expand Up @@ -67,20 +68,24 @@ generate-protos:
.PHONY: docker-setup
docker-setup:
@if [ "$(DOCKER_COMMAND)" = "docker" ]; then \
docker buildx create --use --name hobbyfarm || true; \
docker buildx inspect --bootstrap; \
if "$(DOCKER_COMMAND)" buildx inspect "$(BUILDX_BUILDER)" >/dev/null 2>&1; then \
"$(DOCKER_COMMAND)" buildx use "$(BUILDX_BUILDER)"; \
else \
"$(DOCKER_COMMAND)" buildx create --use --name "$(BUILDX_BUILDER)"; \
fi; \
"$(DOCKER_COMMAND)" buildx inspect "$(BUILDX_BUILDER)" --bootstrap; \
fi

.PHONY: docker-build
docker-build:
@for service in $(SERVICES); do \
if [ "$$service" = "gargantua" ]; then \
echo "Building gargantua..."; \
$(DOCKER_COMMAND) $(DOCKER_BUILD_COMMAND) $(if $(PUSH),--push,--load) $(PLATFORM_ARG) --file $(GARGANTUA_DOCKERFILE) --tag $(IMAGE_REGISTRY)/$$service:$(IMAGE_TAG) .; \
$(DOCKER_COMMAND) $(DOCKER_BUILD_COMMAND) $(if $(PUSH),--push,--load) $(PLATFORM_ARG) --file "$(GARGANTUA_DOCKERFILE)" --tag "$(IMAGE_REGISTRY)/$$service:$(IMAGE_TAG)" "."; \
else \
echo "Building $$service..."; \
image_tag=$(IMAGE_REGISTRY)/$${service/%svc/-service}:$(IMAGE_TAG); \
$(DOCKER_COMMAND) $(DOCKER_BUILD_COMMAND) $(if $(PUSH),--push,--load) $(PLATFORM_ARG) --build-arg SERVICE_NAME=$$service --file $(SERVICE_DOCKERFILE) --tag $$image_tag .; \
$(DOCKER_COMMAND) $(DOCKER_BUILD_COMMAND) $(if $(PUSH),--push,--load) $(PLATFORM_ARG) --build-arg SERVICE_NAME="$$service" --file "$(SERVICE_DOCKERFILE)" --tag "$$image_tag" "."; \
fi; \
done

Expand Down
19 changes: 19 additions & 0 deletions v3/services/quizsvc/internal/quizevaluation/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ func NewPreparedQuizEvaluation(validationType quiz.ValidationType, quizEvaluatio
}
}

func NewPreparedQuizEvaluationReport(quizEvaluation *quizpb.QuizEvaluation) PreparedQuizEvaluationReport {
attempts := make([]PreparedQuizEvaluationReportAttempt, len(quizEvaluation.GetAttempts()))
for i, attempt := range quizEvaluation.GetAttempts() {
attempts[i] = PreparedQuizEvaluationReportAttempt{
Timestamp: attempt.GetTimestamp(),
Score: attempt.GetScore(),
Pass: attempt.GetPass(),
}
}

return PreparedQuizEvaluationReport{
Id: quizEvaluation.GetId(),
Quiz: quizEvaluation.GetQuiz(),
User: quizEvaluation.GetUser(),
Scenario: quizEvaluation.GetScenario(),
Attempts: attempts,
}
}

func NewPBQuizEvaluationAttemptForStart(attempt uint32, quiz *quizpb.Quiz) *quizpb.QuizEvaluationAttempt {
var selects map[string]*generalpb.StringArray
if quiz.GetShuffle() {
Expand Down
32 changes: 32 additions & 0 deletions v3/services/quizsvc/internal/quizevaluation/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,35 @@ func TestNewPreparedAttempt(t *testing.T) {
})
}
}

func TestNewPreparedQuizEvaluationReport(t *testing.T) {
evaluation := &quizpb.QuizEvaluation{
Id: "evaluation-id",
Quiz: "quiz-id",
User: "user-id",
Scenario: "scenario-id",
Attempts: []*quizpb.QuizEvaluationAttempt{
{
Timestamp: "completed-at",
Score: 80,
Pass: true,
Corrects: map[string]*generalpb.StringArray{
"question-id": {Values: []string{"answer-id"}},
},
Selects: map[string]*generalpb.StringArray{
"question-id": {Values: []string{"answer-id"}},
},
},
},
}

assert.Equal(t, PreparedQuizEvaluationReport{
Id: "evaluation-id",
Quiz: "quiz-id",
User: "user-id",
Scenario: "scenario-id",
Attempts: []PreparedQuizEvaluationReportAttempt{
{Timestamp: "completed-at", Score: 80, Pass: true},
},
}, NewPreparedQuizEvaluationReport(evaluation))
}
37 changes: 37 additions & 0 deletions v3/services/quizsvc/internal/quizevaluation/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,43 @@ func NewQuizEvaluationService(
}
}

func (qes QuizEvaluationService) ListFunc(w http.ResponseWriter, r *http.Request) {
user, err := rbac.AuthenticateRequest(r, qes.authnClient)
if err != nil {
util.ReturnHTTPMessage(w, r, http.StatusUnauthorized, "unauthorized", "authentication failed")
return
}

impersonatedUserId := user.GetId()
authrResponse, err := rbac.AuthorizeSimple(r, qes.authrClient, impersonatedUserId, rbac.HobbyfarmPermission(rbac.ResourcePluralQuizEvaluation, rbac.VerbList))
if err != nil || !authrResponse.Success {
util.ReturnHTTPMessage(w, r, http.StatusForbidden, "forbidden", "no access to list quiz evaluations")
return
}

quizEvaluationList, err := qes.internalServer.ListQuizEvaluation(r.Context(), &generalpb.ListOptions{})
if err != nil {
glog.Errorf("error while listing all quiz evaluations: %s", hferrors.GetErrorMessage(err))
util.ReturnHTTPMessage(w, r, http.StatusInternalServerError, "error", "error listing all quiz evaluations")
return
}

preparedQuizEvaluations := make([]PreparedQuizEvaluationReport, 0, len(quizEvaluationList.GetQuizEvaluations()))
for _, quizEvaluation := range quizEvaluationList.GetQuizEvaluations() {
preparedQuizEvaluations = append(preparedQuizEvaluations, NewPreparedQuizEvaluationReport(quizEvaluation))
}

encodedQuizEvaluations, err := json.Marshal(preparedQuizEvaluations)
if err != nil {
glog.Errorf("error marshalling quiz evaluations: %v", err)
util.ReturnHTTPMessage(w, r, http.StatusInternalServerError, "error", "error listing all quiz evaluations")
return
}

util.ReturnHTTPContent(w, r, http.StatusOK, "success", encodedQuizEvaluations)
glog.V(2).Info("retrieved list of all quiz evaluations")
}

func (qes QuizEvaluationService) GetFunc(w http.ResponseWriter, r *http.Request) {
user, err := rbac.AuthenticateRequest(r, qes.authnClient)
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions v3/services/quizsvc/internal/quizevaluation/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ type PreparedQuizEvaluation struct {
Attempts []PreparedAttempt `json:"attempts"`
}

type PreparedQuizEvaluationReport struct {
Id string `json:"id"`
Quiz string `json:"quiz"`
User string `json:"user"`
Scenario string `json:"scenario"`
Attempts []PreparedQuizEvaluationReportAttempt `json:"attempts"`
}

type PreparedQuizEvaluationReportAttempt struct {
Timestamp string `json:"timestamp,omitempty"`
Score uint32 `json:"score"`
Pass bool `json:"pass"`
}

type PreparedAttempt struct {
CreationTimestamp string `json:"creation_timestamp"`
Timestamp string `json:"timestamp,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions v3/services/quizsvc/internal/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func (qs QuizServer) SetupRoutes(r *mux.Router) {
r.HandleFunc("/a/quiz/{id}/delete", qs.internalQuizService.DeleteFunc).Methods("DELETE")
r.HandleFunc("/quiz/{id}", qs.internalQuizService.GetForUserFunc).Methods("GET")
// quiz score
r.HandleFunc("/a/quiz/evaluation/list", qs.internalQuizEvaluationService.ListFunc).Methods("GET")
r.HandleFunc("/a/quiz/evaluation/{id}", qs.internalQuizEvaluationService.GetFunc).Methods("GET")
r.HandleFunc("/a/quiz/evaluation/{id}/delete", qs.internalQuizEvaluationService.DeleteFunc).Methods("DELETE")
r.HandleFunc("/quiz/evaluation/start", qs.internalQuizEvaluationService.StartFunc).Methods("POST")
Expand Down
23 changes: 19 additions & 4 deletions v3/services/rbacsvc/internal/rbac/rbac_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hobbyfarm/gargantua/v3/pkg/util"
wranglerRbac "github.com/rancher/wrangler/pkg/generated/controllers/rbac"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)
Expand All @@ -29,10 +30,10 @@ func list() []Role {
addRule([]string{"hobbyfarm.io"}, []string{"list", "get"}, []string{"users"}).
addRule([]string{"rbac.authorization.k8s.io"}, []string{"*"}, []string{"roles", "rolebindings"})
}),
// Content Creator can create and edit scenarios and courses
// Content Creator can create and edit scenarios, courses and quiz
newRole("content-creator", func(r Role) Role {
return r.
addRule([]string{"hobbyfarm.io"}, []string{"*"}, []string{"scenarios", "courses"}).
addRule([]string{"hobbyfarm.io"}, []string{"*"}, []string{"scenarios", "courses", "quizes"}).
addRule([]string{"hobbyfarm.io"}, []string{"list", "get"}, []string{"virtualmachinetemplates"})
}),
// Infatructure Admin can edit environments and vmtemplates
Expand All @@ -52,7 +53,7 @@ func list() []Role {
// ScheduledEvent Proctor is allowed to view scheduled events + dashboards
newRole("scheduledevent-proctor", func(r Role) Role {
return r.
addRule([]string{"hobbyfarm.io"}, []string{"list", "get"}, []string{"scheduledevents", "accesscodes", "scenarios", "courses", "environments", "virtualmachinetemplates", "virtualmachinesets", "users"}).
addRule([]string{"hobbyfarm.io"}, []string{"list", "get"}, []string{"scheduledevents", "accesscodes", "scenarios", "courses", "environments", "virtualmachinetemplates", "virtualmachinesets", "users", "quizes", "quizevaluations"}).
addRule([]string{"hobbyfarm.io"}, []string{"list"}, []string{"environments"}).
addRule([]string{"hobbyfarm.io"}, []string{"list", "get", "watch"}, []string{"progresses", "virtualmachines", "virtualmachineclaims"}).
addRule([]string{"hobbyfarm.io"}, []string{"update", "delete", "list", "get"}, []string{"sessions"})
Expand All @@ -78,7 +79,21 @@ func Create(ctx context.Context, cfg *rest.Config) error {
roles := list()
rf := factory.Rbac().V1().Role()
for _, role := range roles {
rf.Create(&role.r)
existing, err := rf.Get(role.r.Namespace, role.r.Name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
if _, err = rf.Create(&role.r); err != nil {
return err
}
continue
}
if err != nil {
return err
}

role.r.ResourceVersion = existing.ResourceVersion
if _, err = rf.Update(&role.r); err != nil {
return err
}
}

return nil
Expand Down
Loading