Skip to content
Snippets Groups Projects
Commit a93fe7a2 authored by michael.minelli's avatar michael.minelli
Browse files

Merge branch 'move-wiki-to-website' into v3.6.0

parents 8ac1b19c f9563799
No related branches found
No related tags found
1 merge request!10Resolve "Add sonar integration"
Pipeline #29869 passed
# DojoCLI
# Documentation of `The Dojo CLI` utility
More informations about the DojoCLI can be found in the [wiki](https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/wikis/home).
\ No newline at end of file
All documentations are available on the [Dojo documentation website](https://www.hepiapp.ch/) : https://www.hepiapp.ch/.
\ No newline at end of file
# Dojo: a platform to practice programming
The dojo platform is an online tool built to help practice programming by
allowing users to propose assignments and perform them as exercises.
The tool is very flexible and allows for proposing exercises for any language
and does not impose any limitation on a framework to be heavily relying
on Docker and Gitlab. These tools used in combination allow for automatic
correction of assignments in order to give immediate feedback to users
performing exercises. Solved exercises can then be shared among the community
of users such that they can inspire other users or give hints on ways to solve
a given exercise.
The two major concepts of the platform are the **assignments** and the **exercises**.
The principal way to interact with the Dojo platform is currently the `dojo` CLI.
## The assignment
An assignment is written by a user that wants to propose an exercise. It is typically composed of a written description of the work to be performed,
and tests that must be passed once the exercise is successfully performed (and some configuration files for the infrastructure of the tests
such as docker files). At its core, an assignment is
nothing else than a git repository that can be forked in the form of an exercise and modified using standard git commands.
For a more detailed description please see the [CLI documentation](home).
An assignment can be proposed by any user.
In the future a dependency tree of assignments can be created, as well as tagging for filtering purposes.
## The exercise
An exercise is an instance of an assignment which the learner will modify in order to make it pass the automatic tests.
It can be run locally on any user's machine using the dojo CLI. When the exercise is completed
it is pushed on the dojo where the CI/CD tools of Gitlab can evaluate it automatically and
notify the dojo platform of the result. The exercises can then be shared with other users
in order to propose a wide variety of solutions and can be a base for discussion among users
and with teachers.
For a more detailed description please see the [CLI documentation](home).
\ No newline at end of file
# How to setup your development environment
## Introduction
This tutorial describes how to setup your development environment for building the Dojo CLI by detailing the prerequisites and dependencies needed.
## Technologies
The cli is built using [NodeJS](https://nodejs.org/en/) and [NPM](https://www.npmjs.com/).
The programming language used is [Typescript](https://www.typescriptlang.org/) v5.
## Prerequisites
In order to build the cli you will need the following tools:
- [NodeJS](https://nodejs.org/en/) (version 18 or higher)
- [NPM](https://www.npmjs.com/) (version 10 or higher)
Install NodeJS and NPM by following the instructions on the [official website](https://nodejs.org/en/download/package-manager).
Or via Node Version Manager (NVM) by following the instructions on the [official website](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
## Dependencies
The CLI is packaged using [pkg](https://www.npmjs.com/package/pkg).
This means that all the dependencies are bundled in the final binary.
Here are the main dependencies used by the cli (you don't need to install them manually or globally on your system):
- [Axios](https://www.npmjs.com/package/axios): a promise-based HTTP client for the browser and Node.js. It is
used to make HTTP(S) requests to the Dojo backend and Gitlab.
- [Boxen](https://www.npmjs.com/package/boxen): used to display messages in a box in the terminal.
- [Chalk](https://www.npmjs.com/package/chalk): used to add colors to the messages in the terminal.
- [Commander.js](https://www.npmjs.com/package/commander): a library to write command-line interfaces.
- [Dotenv](https://www.npmjs.com/package/dotenv): used to load environment variables from a .env file.
- [Dotenv-vault](https://www.npmjs.com/package/dotenv-vault): a CLI to sync .env files across machines,
environments, and team members.
- [Inquirer](https://www.npmjs.com/package/inquirer): used to ask perform to the interactive command line user interfaces.
- [JsonWebToken](https://www.npmjs.com/package/jsonwebtoken): used to generate and validate [JSON Web Tokens](https://jwt.io/).
- [ora](https://www.npmjs.com/package/ora): used to display elegantly in the terminal.
- [zod](https://www.npmjs.com/package/zod): a TypeScript-first schema validation with static type inference. Used
in the projet to validate json files created by the user.
## Installation
First of all, you need to clone the repository:
```bash
$ git clone --recurse-submodule ssh://git@ssh.hesge.ch:10572/dojo_project/projects/ui/dojocli.git
```
Then, you need to move to the project's directory:
```bash
$ cd NodeApp
```
To install the dependencies listed above you can use the following command in the base directory of the project:
```bash
$ npm install
```
## Environment variables
Environment variables are used to store sensitive information such as API keys, passwords, etc.
They are also used to store configuration information that can be changed without modifying the code.
You can decrypt env var stored in the `.env.vault` file with the following commands in the project's main folder:
```bash
> npx dotenv-vault local keys
environment DOTENV_KEY
─────────── ─────────────────────────────────────────────────────────────────────────
development dotenv://:key_1234@dotenv.local/vault/.env.vault?environment=development
Set DOTENV_KEY on your server
> npx dotenv-vault local decrypt dotenv://:key_1234@dotenv.local/vault/.env.vault?environment=development > .env.development
```
**The `.env.keys` file have to be requested to the project maintainer: [Michaël Minelli](mailto:dojo@minelli.me).**
## Run the cli
To run the cli (in dev mode) you can use the following command in the base directory of the project:
```bash
$ npm run start:dev -- COMMAND
```
Where `COMMAND` is the command you want to run.
For example, if you want to test the exercise creation command you can use the following command:
```bash
$ npm run start:dev -- exercise create -a "Technique de compilation - TP" --members_username michael.minelli
```
\ No newline at end of file
# How to add a new command to the Dojo cli
## Introduction
This tutorial describes how to add a new command to the Dojo cli. For that we take an existing command and describe
his implementation. This command allow a member of the teaching staff of an assignment to publish it.
The command is named `publish` and is a subcommand of the `assignment` command. Here is the command structure:
```bash
dojo assignment publish [options] <assignment_name_or_url>
```
This tutorial is linked to another one which explains how to add a new route to the Dojo backend.
The documentation for this is available on the [Dojo backend wiki](https://gitedu.hesge.ch/dojo/backend/-/wikis/Development/1-How-to-add-a-new-route).
For the rest of this tutorial we will assume that we are in the `NodeApp` folder :
```bash
cd NodeApp
```
## Prerequisites
All the prerequisites are described in
[How to setup your development environment](1-How-to-setup-your-development-environment) tutorial.
## Commands files structure
The commands files are located in the `src/commander` folder. All command files are named with the following pattern:
`commandPathCommand.ts` where `commandPath` has to be replaced by the path of the command (command, subcommand,
subsubcommand, etc.).
For each command there are two choices:
1. If it's a command that will contains subcommands, you will need to create a folder with the name of the command.
- In this folder you will need to create a file with the name of the command.
- A subfolder named `subcommands` will be needed to store the subcommands files or folders.
2. If it's a command that will not contains subcommands, you will need to create a file with the name of the command.
### Apply to our use case
In our case we will create a command that will be a subcommand of the `assignment` command, so its file path will be :
`src/commander/assignment/subcommands/AssignmentPublishCommand.ts`.
## Command class inheritance
All commands must inherit from the `CommanderCommand` abstract class. This class is located in the
`src/commander/CommanderCommand.ts` file.
When you inherit from this class you will need to implement the following methods and properties :
- `commandName: string`: This property will be used to define the name of the (sub)command.
- `defineCommand(): void`: This method will be used to define the command itself.
- `commandAction(...args: Array<unknown>): Promise<void>`: This method will be used to define the action of the command.
You will need to link the command to the action by writing this code in the defineCommand method:
```typescript
this.command.action(this.commandAction.bind(this));
```
Optionally, you can implement the `defineSubCommands(): void` method if you want to declare some subcommands.
Here is an exemple of implementation:
```typescript
protected defineSubCommands() {
SessionLoginCommand.registerOnCommand(this.command);
SessionLogoutCommand.registerOnCommand(this.command);
SessionTestCommand.registerOnCommand(this.command);
}
```
### Apply to our use case
Now, the `src/commander/assignment/subcommands/AssignmentPublishCommand.ts` file will look like this:
```typescript
import CommanderCommand from '../../CommanderCommand';
class AssignmentPublishCommand extends CommanderCommand {
protected commandName: string = 'publish';
protected defineCommand() {
...
}
protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
...
}
}
export default new AssignmentPublishCommand();
```
## Define the command
In the `defineCommand()` method you will need to define the command itself. To do this you will need to use the
Commander.js library. The documentation is available [here](https://github.com/tj/commander.js).
### Apply to our use case
We want to define the `publish` command that take the name of the assignment (or his url) as an argument and have a
`--force` option that will allow the user to publish the assignment without having to confirm the action.
Here is the code to add in the `defineCommand()` method:
```typescript
protected defineCommand() {
this.command
.description('publish an assignment')
.argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
.option('-f, --force', 'don\'t ask for confirmation')
.action(this.commandAction.bind(this));
}
```
## Define the action
To define the action we must adapt the `commandAction()` method by adding arguments and options defined in the command
definition.
And then we will need to implement the action of the command.
### Apply to our use case
In our case we will need to adapt the `commandAction()` method like this:
```typescript
protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
...
}
```
For the implementation we will split our code into several steps:
1. Options parsing and confirmation asking
2. Checking / Retrieving data
1. Test the session validity
2. Retrieve the assignment and check if it exists
3. Check if the user is in the teaching staff of the assignment
4. Check if the assignment is publishable by getting the last pipeline status
3. Publishing the assignment
#### 1. Options parsing and confirmation asking
First we need to parse the options and check if the `--force` option is present. If it's not present we ask
the user to confirm the action.
```typescript
if ( !options.force ) {
options.force = (await inquirer.prompt({
type : 'confirm',
name : 'force',
message: 'Are you sure you want to publish this assignment?'
})).force;
}
if ( !options.force ) {
return;
}
```
#### 2. Checking / Retrieving data
##### 2.1. Test the session validity
We call the SessionManager singleton that contain a function to test the session validity.
It checks only the Dojo backend session validity and not the Gitlab one.
```typescript
if ( !await SessionManager.testSession(true, null) ) {
return;
}
```
##### 2.2. Retrieve the assignment and check if it exists
We call the DojoBackendManager singleton that contains a function to retrieve an assignment by his name or his url.
This function make a request to the Dojo backend to retrieve the assignment.
```typescript
assignment = await DojoBackendManager.getAssignment(assignmentNameOrUrl);
if ( !assignment ) {
return;
}
```
##### 2.3. Check if the user is in the teaching staff of the assignment
We check if the user is in the teaching staff of the assignment by verifying if his id is in the staff array.
```typescript
if ( !assignment.staff.some(staff => staff.id === SessionManager.profile?.id) ) {
return;
}
```
##### 2.4. Check if the assignment is publishable by getting the last pipeline status
We call the SharedAssignmentHelper singleton that contains a function to check if the assignment is publishable
(an assignment is publishable only if the last pipeline status is `success`).
This function make a request to the Gitlab API to retrieve the last pipeline of the assignment and return an object
with the informations of it including a `isPublishable` state that is true only if the status of the pipeline is
`success`.
```typescript
const isPublishable = await SharedAssignmentHelper.isPublishable(assignment.gitlabId);
if ( !isPublishable.isPublishable ) {
return;
}
```
#### 3. Publishing the assignment
Finally, we will call the DojoBackendManager singleton that contain a function to publish an assignment as will be described hereafter.
First of all, we need to implement the route on the backend. For that, please refer to the linked tutorial on the
[Dojo backend API wiki](https://gitedu.hesge.ch/dojo/backend/-/wikis/Development/1-How-to-add-a-new-route).
Then, we need to create a function that will call the newly created route. This function needs to know the route
that we have previously created. We will store this route in the `src/sharedByClients/types/Dojo/ApiRoutes.ts` file.
```typescript
export enum ApiRoute {
...
ASSIGNMENT_PUBLISH = '/assignment/{{nameOrUrl}}/publish',
ASSIGNMENT_UNPUBLISH = '/assignment/{{nameOrUrl}}/unpublish',
...
}
```
Then, we can create the function that will be located in the backend manager singleton stored in the
`src/managers/DojoBackendManager.ts` file.
_Note:_ We use the `axios` library to make the request to the backend. We do not need to fill authorization headers
because the `axios interceptors` defined in the `src/managers/HttpManager.ts` file will do it for us.
```typescript
public async changeAssignmentPublishedStatus(assignment: Assignment, publish: boolean, verbose: boolean = true) {
try {
await axios.patch<DojoBackendResponse<null>>(this.getApiUrl(publish ? ApiRoute.ASSIGNMENT_PUBLISH : ApiRoute.ASSIGNMENT_UNPUBLISH).replace('{{nameOrUrl}}', encodeURIComponent(assignment.name)), {});
} catch ( error ) {
...
throw error;
}
}
```
Finally, we go back to the command file and we call the function to publish the assignment.
```typescript
try {
await DojoBackendManager.changeAssignmentPublishedStatus(assignment, true);
} catch ( error ) {
return;
}
```
## Use case: final code
In the examples above we have seen all the different steps to implement our `publish` subcommand.
For a better readability all the UI code (done with ora library) have been removed from the snippets.
Here is the final code of the `src/commander/assignment/subcommands/AssignmentPublishCommand.ts` file with integration
of ora calls for the interface.
### AssignmentPublishCommand.ts
```typescript
import CommanderCommand from '../../CommanderCommand';
import inquirer from 'inquirer';
import Assignment from '../../../sharedByClients/models/Assignment';
import chalk from 'chalk';
import SessionManager from '../../../managers/SessionManager';
import ora from 'ora';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
class AssignmentPublishCommand extends CommanderCommand {
protected commandName: string = 'publish';
protected defineCommand() {
this.command
.description('publish an assignment')
.argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
.option('-f, --force', 'don\'t ask for confirmation')
.action(this.commandAction.bind(this));
}
protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
if ( !options.force ) {
options.force = (await inquirer.prompt({
type : 'confirm',
name : 'force',
message: 'Are you sure you want to publish this assignment?'
})).force;
}
if ( !options.force ) {
return;
}
let assignment!: Assignment | undefined;
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
if ( !await SessionManager.testSession(true, null) ) {
return;
}
ora('Checking assignment:').start().info();
ora({
text : assignmentNameOrUrl,
indent: 4
}).start().info();
const assignmentGetSpinner: ora.Ora = ora({
text : 'Checking if assignment exists',
indent: 8
}).start();
assignment = await DojoBackendManager.getAssignment(assignmentNameOrUrl);
if ( !assignment ) {
assignmentGetSpinner.fail(`The assignment doesn't exists`);
return;
}
assignmentGetSpinner.succeed(`The assignment exists`);
const assignmentCheckAccessSpinner: ora.Ora = ora({
text : 'Checking accesses',
indent: 8
}).start();
if ( !assignment.staff.some(staff => staff.id === SessionManager.profile?.id) ) {
assignmentCheckAccessSpinner.fail(`You are not in the staff of this assignment`);
return;
}
assignmentCheckAccessSpinner.succeed(`You are in the staff of this assignment`);
const assignmentIsPublishable: ora.Ora = ora({
text : 'Checking if the assignment is publishable',
indent: 8
}).start();
const isPublishable = await SharedAssignmentHelper.isPublishable(assignment.gitlabId);
if ( !isPublishable.isPublishable ) {
assignmentIsPublishable.fail(`The assignment is not publishable: ${ isPublishable.status?.message }`);
return;
}
assignmentIsPublishable.succeed(`The assignment is publishable`);
}
{
console.log(chalk.cyan(`Please wait while we publish the assignment...`));
try {
await DojoBackendManager.changeAssignmentPublishedStatus(assignment, true);
} catch ( error ) {
return;
}
}
}
}
export default new AssignmentPublishCommand();
```
### ApiRoutes.ts
```typescript
export enum ApiRoute {
...
ASSIGNMENT_PUBLISH = '/assignment/{{nameOrUrl}}/publish',
ASSIGNMENT_UNPUBLISH = '/assignment/{{nameOrUrl}}/unpublish',
...
}
```
### DojoBackendManager.ts
```typescript
public async changeAssignmentPublishedStatus(assignment: Assignment, publish: boolean, verbose: boolean = true) {
const spinner: ora.Ora = ora('Changing published status...');
if ( verbose ) {
spinner.start();
}
try {
await axios.patch<DojoBackendResponse<null>>(this.getApiUrl(publish ? ApiRoute.ASSIGNMENT_PUBLISH : ApiRoute.ASSIGNMENT_UNPUBLISH).replace('{{nameOrUrl}}', encodeURIComponent(assignment.name)), {});
if ( verbose ) {
spinner.succeed(`Assignment ${ assignment.name } successfully ${ publish ? 'published' : 'unpublished' }`);
}
return;
} catch ( error ) {
if ( verbose ) {
if ( error instanceof AxiosError && error.response ) {
spinner.fail(`Assignment visibility change error: ${ error.response.statusText }`);
} else {
spinner.fail(`Assignment visibility change error: unknown error`);
}
}
throw error;
}
}
```
\ No newline at end of file
# How to perform an exercise
In this tutorial we quickly explain the workflow for performing an exercise by a student (or a professor).
The exercise is based on the `c_hello_world` assignment created in [assignment creation](1-Assignment-creation) tutorial.
## Exercise "creation"
To perform an exercise the student must first create the exercise. Under the hood, this operation consist in making
a fork of a published assignment in the student's namespace.
This is performed by the following `dojo` command:
```bash
$ dojo exercise create --assignment c_hello_world
```
```console
Please wait while we verify and retrieve data...
ℹ Checking Dojo session:
✔ The session is valid
✔ Student permissions
ℹ Checking Gitlab token:
✔ Read access
✔ Write access
ℹ Checking assignment:
✔ Assignment "c_hello_world" exists
✔ Assignment "c_hello_world" is published
Please wait while we are creating the exercise...
✔ Exercise successfully created
ℹ Id: 8d3f53a0-0d32-4455-a251-e1a1c5a97c6a
ℹ Name: DojoEx - c_hello_world - orestis.malaspin - 1
ℹ Web URL: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a
ℹ HTTP Repo: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a.git
ℹ SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a.git
```
## To perform the exercise
The exercise is nothing else than a git repository so the workflow is pretty straightforward.
1. Clone the repository (see the repo link above)
```bash
$ git clone ssh://git@ssh.hesge.ch:10572/dojo/exercise/dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a.git
```
```console
Cloning into 'dojo-ex_c_hello_world_8d3f53a0-0d32-4455-a251-e1a1c5a97c6a'...
remote: Enumerating objects: 33, done.
remote: Counting objects: 100% (33/33), done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 33 (delta 6), reused 16 (delta 3), pack-reused 0
Receiving objects: 100% (33/33), 7.32 KiB | 7.32 MiB/s, done.
Resolving deltas: 100% (6/6), done.
```
2. Read the `README.md` file to understand what is expected by the teacher.
3. Modify/create the appropriate code.
4. (Optional but recommended) Execute the pipeline locally
```bash
$ dojo exercise run
```
```console
Please wait while we are checking and creating dependencies...
ℹ Checking exercise content:
✔ The exercise folder contains all the needed files
✔ The dojo_assignment.json file is valid
✔ The Docker deamon is running
Please wait while we are running the exercise...
✔ Docker Compose file run successfully
✔ Linked services logs acquired
✔ Containers stopped and removed
Please wait while we are checking the results...
✔ Results file found
✔ Results file is valid
✔ Results folder size is in bounds
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ Global result : ❌ Failure ┃
┃ ┃
┃ Execution exit code : 0 ┃
┃ ┃
┃ Execution results folder : /home/student/DojoExecutions/dojo_execLogs_2023-08-30T18_58_05_764Z ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```
Currently the exercise is a failure which is sad but expected since there was no modification in any file.
One can see that we have access to a `results` folder in `/home/student/DojoExecutions/dojo_execLogs_2023-08-30T18_58_05_764Z`.
This directory contains the output of the pipeline execution
```console
/home/student/DojoExecutions/dojo_execLogs_2023-08-30T18_58_05_764Z
├── Dojo
│ ├── dockerComposeLogs.txt
│ └── results.json
└── Exercise
└── diff_output.txt
2 directories, 3 files
```
In particular one can find the output the teacher wanted us (students) to see in the `Exercise` folder.
It contains any output file that can be used for debugging for example. In this case it contains
```console
< ./hello_world
< (null)
\ No newline at end of file
---
> Hello world!
\ No newline at end of file
```
One can see that there is an `Hello world!` expected and that no `Hello world!` was provided by our program.
One can also see the complete
logs of the `docker compose` command in `Dojo/dockerComposeLogs.txt` which may (or may not)
provide additional informations.
In order to complete this assignment we have to modify the `src/function.c` file such that it becomes
```c
#include "function.h"
char *hello_world()
{
return "Hello world!";
}
```
Rerunning the pipeline now yields
```bash
$ dojo exercise run
```
```console
Please wait while we are checking and creating dependencies...
ℹ Checking exercise content:
✔ The exercise folder contains all the needed files
✔ The dojo_assignment.json file is valid
✔ The Docker deamon is running
Please wait while we are running the exercise...
✔ Docker Compose file run successfully
✔ Linked services logs acquired
✔ Containers stopped and removed
Please wait while we are checking the results...
✔ Results file found
✔ Results file is valid
✔ Results folder size is in bounds
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ Global result : ✅ Success ┃
┃ ┃
┃ Execution exit code : 0 ┃
┃ ┃
┃ Execution results folder : /home/student/DojoExecutions/dojo_execLogs_2023-08-30T19_20_25_104Z ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```
5. Now that we are happy with the state of our exercise, we can commit and push our modifications and
the exercise will be run through the Gitlab CI pipeline and one can see the "official" result
of our exercise.
![The Gitlab CI pipeline succeeded!](pipeline.png)
It is not necessary to have a successful pipeline to commit and push the code. We can do it at any time
which is particularly useful when doing long assignments that take many iterations to finish.
# How to create an assignment
We will describe how to create a very simple assignment with dojo.
Here we will build a `Hello world!` in the C programming language. We will describe how one must
modify the default template to have an exercise.
The exercise will be to fill a function to return the `Hello world!` string.
The output of the function will be printed on the standard output.
The structure will be provided to the students as well as a very simple `Makefile`
to compile and run the code.
The success or failure of this assignment will be tested by comparing the output
of our program with an the expected output (the famous `Hello world!`).
To build this exercise we need to perform several steps that will be described below in great details.
## Empty assignment creation
First create a new assignment with the command
```bash
$ dojo assignment create --name c_hello_world
```
```console
Please wait while we verify and retrieve data...
ℹ Checking Dojo session:
✔ The session is valid
✔ Teaching staff permissions
ℹ Checking Gitlab token:
✔ Read access
✔ Write access
✔ Assignment name "c_hello_world" is available
Please wait while we are creating the assignment...
✔ Assignment successfully created
ℹ Name: c_hello_world
ℹ Web URL: https://gitedu.hesge.ch/dojo/assignment/c_hello_world
ℹ HTTP Repo: https://gitedu.hesge.ch/dojo/assignment/c_hello_world.git
ℹ SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git
```
## Clone the assignment locally
The assignment is nothing more than a git repository. We just clone it and start
modifying it
```bash
$ git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git
```
```console
Cloning into 'c_hello_world'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 18 (delta 3), reused 18 (delta 3), pack-reused 0
Receiving objects: 100% (18/18), 4.37 KiB | 4.37 MiB/s, done.
Resolving deltas: 100% (3/3), done.
```
The `c_hello_world` assignment has now the following structure
```bash
$ tree c_hello_world/
```
```console
c_hello_world/
├── docker-compose.yml
├── Dockerfile
├── dojo.assignment
└── README.md
```
## Build the development environment
In order to execute a C program we need a working compiler and `make`. In order to
produce a special result file to be parsed by the `dojo` tool to produce nicely formatted output
we will use `jq`, a command-line json creation/edition/reding tool. Our `Dockerfile`
contains
```dockerfile
FROM ubuntu:latest
RUN apt update && apt install gcc make jq -y
COPY . /
ENTRYPOINT ["./run.sh"]
```
We see that we start by installing the required packages on top of the latest Ubuntu image.
We then proceed to copy the complete assignment into the docker container, finall we run
a yet to be created `run.sh` script.
We can test our `Dockerfile` by running the command
```bash
$ docker compose run --build hello_world
```
```console
[+] Building 20.5s (8/8) FINISHED
=> [hello_world internal] load build definition from Dockerfile
=> => transferring dockerfile: 134B
=> [hello_world internal] load .dockerignore
=> => transferring context: 2B
=> [hello_world internal] load metadata for docker.io/library/ubuntu:latest
=> CACHED [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93a3d71a460ff1c44fa6d77
=> [hello_world internal] load build context
=> => transferring context: 39.87kB
=> [hello_world 2/3] RUN apt update && apt install gcc make jq -y
=> [hello_world 3/3] COPY . /
=> [hello_world] exporting to image
=> => exporting layers
=> => writing image sha256:4b561113c7123da08206a2cf2642cb4f331670fe44350646437eaa78e44aff3a
=> => naming to docker.io/library/c_hello_world-hello_world
ERRO[0021] error waiting for container:
Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "./run.sh": stat ./run.sh: no such file or directory: unknown
```
We see an error at the end of the command which is perfectly normal. We did not create the `run.sh` file et (let's leave that for later).
We then need to focus on the `docker-compose.yml` file which will take care of all the hard work
needed if complex workflows are required. Here it is as simple as possible and should contain
```yml
services:
hello_world:
container_name: hello_world
build:
context: ./
dockerfile: Dockerfile
volumes:
- hello_world_volume:/result # <hello_world_volume> must be the same as below but
# the name may be arbitrary. The volume is optional but
# you can provide it for show details of the execution like
# tests passed or not or simply any log file you think that
# can be useful for the students. If it's present, this volume
# must be present in the dojo_assignment.json file under the field
# "result": {
# "volume": "hello_world_volume",
# ...
# }
volumes:
hello_world_volume:
```
In this file, we see the definition of a `hello_world_volume` this is an arbitrary name and can be changed but it
must be coherent in this file and in the `dojo_assignment.json` file (more on this later configuration file in [The dojo configuration file](#the-dojo-configuration-file)).
This (optional) volume is responsible of mounting `/result/` directory which will contain all
the output generated by our assignment (again the name can be changed). In particular
in this director he will be required to create a `dojo_assignment.json` file
that contains at least if the assignment was successfully performed (more on that at the end of the [Creating the assignment files](#creating-the-assignment-files) section).
## The dojo configuration file
The `dojo_assignment.json` file contains the general configuration of the assignment.
The configuration parameters are:
- the `dojoAssignmentVersion` which define the version of the dojo assignment file (actually only version 1 is available),
- the `version` of you assignment,
- the `immutable` field which are files or directories that will be overwritten when the compilation pipeline is run (even if the student
modifies these files the modifications will not be taken into account). Each immutable is defined by:
- `path` **(required)**: the path of the immutable file or directory,
- `description` _(optional)_: provides a description of the immutable for the students or Dojo interface,
- `isDirectory` _(optional)_: `true` if the immutable is a directory, `false` otherwise (default),
- the `results` which provide information to the Dojo for finding results of the execution. The *result* field is defined by:
- `container` **(required)**: the name of the service in the docker compose file that will be run (his dependencies will be run automatically). In our case it is `hello_world`,
- `volume` _(optional)_: this field (`hello_world_volume`) corresponds to the `volumes` field in the `docker-compose.yml` file.
In its default form `dojo_assignment.json` file contains
```json
{
"dojoAssignmentVersion": 1,
"version": 1,
"immutable": [
{
"description": "Dockerfile of the unique container",
"path": "Dockerfile",
"isDirectory": false
}
],
"result": {
"container": "hello_world",
"volume": "hello_world_volume"
}
}
```
This file will be completed in [The immutable files](#the-immutable-files) section with the files of our assignment that will be
created in the [next section](#creating-the-assignment-files).
## Creating the assignment files
For this assignment we will create the following files with the following content:
- `src/Makefile`
```makefile
CC:=gcc
CFLAGS:=-Wall -Wextra -Wpedantic -fsanitize=address -Werror -g
LDFLAGS:=-fsanitize=address
hello_world: hello_world.o function.o
gcc -o $@ $^ $(LDFLAGS)
hello_world.o: function.h
function.o: function.h
run: hello_world
./hello_world
clean:
rm -f *.o hello_world
```
- `src/hello_world.c`
```c
#include <stdio.h>
#include <stdlib.h>
#include "function.h"
int main()
{
printf("%s", hello_world());
return EXIT_SUCCESS;
}
```
- `src/function.h`
```c
#ifndef _FUNCTION_H_
#define _FUNCTION_H_
char *hello_world();
#endif
```
- `src/function.c`
```c
#include "function.h"
char *hello_world()
{
// TODO: Replace 0 here to return "Hello world!"
return 0;
}
```
- `src/expected_output.txt`
```
Hello world!
```
These files will be used to create an executable that will be run by the custom execution script, `run.sh`, see the `Dockerfile` in
the [Build the environment](#build-the-development-environment) section.
All these files have arbitrary names and it's completely up to the teacher to make a coherent exercise.
The only missing file is `run.sh` (do not forget to make it executable, `chmod +x run.sh`) that contains the following code
```bash
#!/bin/bash
echo "Starting tests."
GLOBAL_SUCCESS=false
make -C src clean -s
make -C src -s
if [ $? -eq 0 ]; then
make run -C src > src/output.txt -s
if [ $? -eq 0 ]; then
diff --color src/output.txt src/expected_output.txt > result/diff_output.txt
if [ $? -ne 0 ]; then
echo "Output is wrong:";
cat result/diff_output.txt
exit(1);
else
echo "All tests were a complete success"
GLOBAL_SUCCESS=true
exit(0);
fi
else
echo "Execution failed";
exit(2);
fi
else
echo "Compilation failed."
exit(3);
fi
```
We use the exit code to say to Dojo if the exercise was a success (exit code `0`) or a failure (all other exit codes).
Here one can see the creation of two different files that are located in the `result` directory:
- `result/results.json`
- `result/diff_output.txt`
The `results.json` is mandatory to be created and must at least contain the
`success` field must be `true` or `false` and determines whether the
assignment is a success or a failure. The other files present in the `result` folder
can be retrieved by the students.
In the result directory you can provide the optional `result.json` file that can contain more details about the tests:
```
successfulTests?: number;
failedTests?: number;
successfulTestsList?: Array<string>;
failedTestsList?: Array<string>;
```
- the `successfulTests` which provide the number of successfully passed tests,
- the `failedTests` which provide the number of failed tests,
- the `successfulTestsList` which provide the list (of string) of successfully passed tests,
- the `failedTestsList` which provide the list (of string) of failed tests,
To test if everything works according to plan, one can again use the command
```bash
$ docker compose run --build hello_world
```
```console
[+] Building 1.8s (8/8) FINISHED
=> [hello_world internal] load build definition from Dockerfile
=> => transferring dockerfile: 134B
=> [hello_world internal] load .dockerignore
=> => transferring context: 2B
=> [hello_world internal] load metadata for docker.io/library/ubuntu:latest
=> [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93
=> [hello_world internal] load build context
=> => transferring context: 3.23kB
=> CACHED [hello_world 2/3] RUN apt update && apt install gcc make jq -y
=> [hello_world 3/3] COPY . /
=> [hello_world] exporting to image
=> => exporting layers
=> => writing image sha256:5499aa3aa0b2f2021584dbf46b4b05ab83dc0a5ad4d6c81a3466c56673c3f562
=> => naming to docker.io/library/c_hello_world-hello_world
Starting tests.
Output is wrong:
1c1
< (null)
\ No newline at end of file
---
> Hello world!
\ No newline at end of file
```
where we see that the execution fails. This will be improved in the future and the actual execution as expected to be
run by the students will be added.
## The immutable files
There is only one file that should be modified by the students in this example: the `function.c` file. Therefore we can safely
add all the created files in the `src` except `src/function.c` which is precisely
the file that the student must modify, as well as the `run.sh` file. The `dojo_assignment.json` file becomes
```json
{
"dojoAssignmentVersion": 1,
"version": 1,
"immutable": [
{
"description": "Dockerfile of the unique container",
"path": "Dockerfile",
"isDirectory": false
},
{
"description": "The entry point of the Dockerfile",
"path": "run.sh",
"isDirectory": false
},
{
"description": "The makefile for compilation/execution purposes",
"path": "src/Makefile",
"isDirectory": false
},
{
"description": "Entry point of the code",
"path": "src/hello_world.c",
"isDirectory": false
},
{
"description": "The header file fot the program's assignment",
"path": "src/function.h",
"isDirectory": false
},
{
"description": "The expected output file for comparing with the actual output",
"path": "src/expected_output.txt",
"isDirectory": false
}
],
"result": {
"container": "hello_world",
"volume": "hello_world_volume"
}
}
```
## The `README.md` file
The `README.md` file is used to provide informations on the assignment and the way the teacher wants students to accomplish
the assignment and its content is completely free. It is recommended to use the `Markdown` syntax as the
file extension suggests.
In this assignment the `README.md` file reads
```markdown
# Hello world!
C'est le premier vrai exercice jamais créé sur le Dojo!
Il sert de tutoriel pour créer un exercice simple en C.
## But
Le but est de faire afficher "Hello world!" à notre programme en C.
Pour ce faire, il faut modifier le fichier `src/function.c` sous la ligne
annotée avec `TODO`. Bonne chance!
```
## Validate the assignment
Now that the assignment is ready, we must validate it.
You can do it first locally with the command
```bash
$ dojo assignment check
```
```console
Please wait while we are checking requirements...
✔ Docker daemon is running
✔ All required files exists
Please wait while we are validating dojo_assignment.json file...
✔ dojo_assignment.json file schema is valid
✔ Immutable files are valid
Please wait while we are validating docker compose file...
✔ Docker compose file structure is valid
✔ Docker compose file content is valid
Please wait while we are validating dockerfiles...
✔ Docker compose file content is valid
Please wait while we are running the assignment...
✔ Docker Compose file run successfully
✔ Linked services logs acquired
✔ Containers stopped and removed
┏━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ Global result : ✅ Success ┃
┃ ┃
┃ The assignment is ready to be pushed. ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```
## Publish the work
Now that the assignment is ready, we must publish it. First add/commit/push all the
files needed for your exercise. In this case, it should be:
```console
c_hello_world/
├── docker-compose.yml
├── Dockerfile
├── dojo_assignment.json
├── README.md
├── run.sh
└── src
├── expected_output.txt
├── function.c
├── function.h
├── hello_world.c
└── Makefile
```
Then one must *publish* the assignment for the students to be able to perform to get the exercise.
A pipeline will be automatically run to check that the assignment is valid (the same as the previous state).
Wait for the successfully completion of this pipeline and then you are ready to publish the assignment with the command:
```bash
$ dojo assignment publish c_hello_world
```
```console
? Are you sure you want to publish this assignment? Yes
Please wait while we verify and retrieve data...
ℹ Checking Dojo session:
✔ The session is valid
ℹ Checking assignment:
ℹ c_hello_world
✔ The assignment exists
✔ You are in the staff of this assignment
Please wait while we publish the assignment...
✔ Assignment c_hello_world successfully published
```
The assignment is now ready to be performed by students!
# Installation of the Dojo CLI
1. Download the latest stable version (without "-dev" suffix) from the releases: <https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases>
![releases](../figures/releases.png)
1. Download the executable corresponding to your OS and architecture.
- ℹ️ For these OS you can use specific packaged release that install the binary at the right place (so you can ignore the next point).
- **Debian / Ubuntu** : You can use the `deb` package named `Debian / Ubuntu (YOUR_ARCH) package`.
- **macOS**: You can use the `pkg` version named `macOS (YOUR_ARCH) package`.
2. Put it in your path. For
- For exemple:
- ℹ️ **Linux**: `$HOME/.local/bin`
- ℹ️ **macOS**: `/usr/local/bin`
3. Verify your installation is working correctly by calling the `dojo` CLI.
```bash
dojo
```
```console
Usage: dojo [options] [command]
CLI of the Dojo application
Options:
-h, --help display help for command
-H, --host <string> override the Dojo API endpoint (default: "https://rdps.hesge.ch/dojo/api")
-V, --version output the version number
Commands:
assignment manage an assignment
exercise manage an exercise
help [command] display help for command
session manage Dojo and Gitlab sessions
```
As you can see calling the `dojo` command shows the help menu.
\ No newline at end of file
# Authentication to the Dojo
The authentication is done by the login command:
```bash
dojo session login
```
```console
Please wait while we login you into Dojo...
ℹ Login with Gitlab (https://gitedu.hesge.ch/):
✔ Login server started
⠇ Waiting for user to authorize the application in his web browser
```
At this time the cli opens a web browser to the Gitlab login page. Once you are logged in you will be asked to authorize the application to access your account. Click on the `Authorize` button.
![Authorize](../figures/GitlabLogin.png)
**Note:** If you want to use the cli in a headless environment you can use the `--cli` option. In this case you will be provided with a link to copy/paste in your browser.
Once, the authorization is done, the cli will retrieve the Gitlab token and will try to login to the Dojo backend. If everything is ok you will see the following output (the permissions section may differ depending on your account):
```console
Please wait while we login you into Dojo...
ℹ Login with Gitlab (https://gitedu.hesge.ch/):
✔ Login server started
✔ Login code received
✔ Login server stopped
✔ Gitlab tokens retrieved
ℹ Checking Gitlab token:
✔ Read access
✔ Write access
ℹ Login to Dojo backend:
✔ Logged in
ℹ Checking Dojo session:
✔ The session is valid
✔ Student permissions
✔ Teaching staff permissions
✔ Admin permissions
```
## Test if it is working
```bash
dojo session test
```
```console
ℹ Checking Dojo session:
✔ The session is valid
✔ Teaching staff permissions
✔ Student permissions
ℹ Checking Gitlab token:
✔ Read access
✔ Write access
```
Good news. You can use the Dojo!
# Assignment creation
The assignment is created by a member of the teaching staff and is the basis for students to perform their exercise.
1. To create an assignment
```bash
dojo assignment create --name <unique_name>
```
```console
Please wait while we verify and retrieve data...
ℹ Checking Dojo session:
✔ The session is valid
✔ Teaching staff permissions
ℹ Checking Gitlab token:
✔ Read access
✔ Write access
✔ Assignment name "unique_name" is available
Please wait while we are creating the assignment...
✔ Assignment successfully created
ℹ Name: unique_name
ℹ Web URL: https://gitedu.hesge.ch/dojo/assignment/unique_name
ℹ HTTP Repo: https://gitedu.hesge.ch/dojo/assignment/unique_name.git
ℹ SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/assignment/unique_name.git
```
where `<unique_name>` is the name of the assignment. By default only the creator of the exercise is added to
the assignment (which is just a git repository with some configuration files in it).
There are several other options that can be provided:
```bash
dojo assignment create --name <unique_name> --template <url>
```
where `<url>` is the url of the template repository one may want to use as a basis for the assignment-
```bash
dojo assignment create --name <unique_name> --members_username <usernames>
```
where `<usernames>` is a list of gitedu usernames that will be given the same permissions as the
user creating the assignment.
As usual one can show the help menu by typing
```bash
dojo assignment create --help
```
Or you may just enter (`--help` is implicit in most commands)
```bash
dojo assignment create
```
2. Clone the repository of the assignment that was just created:
```bash
git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/unique_name.git
```
3. Modify the `unique_name` assignment as you want (modify the Dockerfile, docker-compose.yml files, add a readme, source code, compilation tools, etc.). Commit and push our work (soon™ more details will be provided on how to create assignment).
4. Once the assignment is done and validated by the pipeline it must be published to be available to students:
```bash
dojo assignment publish unique_name
```
```console
? Are you sure you want to publish this assignment? Yes
Please wait while we verify and retrieve data...
ℹ Checking Dojo session:
✔ The session is valid
ℹ Checking assignment:
ℹ unique_name
✔ The assignment exists
✔ You are in the staff of this assignment
Please wait while we publish the assignment...
✔ Assignment unique_name successfully published
```
\ No newline at end of file
# Exercise creation
The exercise is an instance of a **published assignment** to be performed by students (or group of students).
1. Create an exercise
```bash
dojo exercise create --assignment unique_name
```
```console
Please wait while we verify and retrieve data...
ℹ Checking Dojo session:
✔ The session is valid
✔ Student permissions
ℹ Checking Gitlab token:
✔ Read access
✔ Write access
ℹ Checking assignment:
✔ Assignment "unique_name" exists
✔ Assignment "unique_name" is published
Please wait while we are creating the exercise...
✔ Exercise successfully created
ℹ Id: some-long-hash
ℹ Name: DojoEx - unique_name - your.name
ℹ Web URL: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_unique_name_some-long-hash
ℹ HTTP Repo: https://gitedu.hesge.ch/dojo/exercise/dojo-ex_unique_name_some-long-hash.git
ℹ SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/exercise/dojo-ex_unique_name_some-long-hash.git
```
**Tips**: You cas use the --members_username or --members_id options to add other students of the group to the exercise.
2. Make changes and try solving the exercise.
3. The complete tests can be run with the following command.
```bash
dojo exercise run
```
```console
Please wait while we are checking and creating dependencies...
ℹ Checking exercise content:
✔ The exercise folder contains all the needed files
✔ The dojo_assignment.json file is valid
✔ The Docker deamon is running
Please wait while we are running the exercise...
✔ Docker Compose file run successfully
✔ Linked services logs acquired
✔ Containers stopped and removed
Please wait while we are checking the results...
✔ Results file found
✔ Results file is valid
✔ Results folder size is in bounds
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ Global result : ✅ Success ┃
┃ ┃
┃ Execution exit code : 0 ┃
┃ ┃
┃ Tests passed : 3 ┃
┃ Tests failed : 1 ┃
┃ ┃
┃ Tests : ┃
┃ - ✅ ListeOrdonnee ┃
┃ - ✅ ListeVide ┃
┃ - ✅ ListeOrdreInverse ┃
┃ - ❌ ListeRandom ┃
┃ ┃
┃ Execution results folder : /home/username/DojoExecutions/dojo_execLogs_2023-08-21T21_33_38_684Z ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```
This command runs the exercise in the current directory or in the path provided by the `--path` option.
4. Once confident enough in the solution, one can commit/push the solution.
This triggers the CI/CD pipeline on gitedu. In the CI/CD all immutable files are overwritten and
the docker-compose is executed (only logs from the current container are given) then other logs
are retrieved from the other images that may be used. The error code of the docker container is retrieved.
Wiki/figures/GitlabLogin.png

1.03 MiB

Wiki/figures/releases.png

522 KiB

# Documentation of the `dojo` CLI utility
# Documentation of `The Dojo CLI` utility
In this wiki you will find the documentation related to the `dojo` CLI.
## Dojo Project
The dojo platform is an online tool built to help practice programming by allowing users to propose assignments and perform them as exercises.
The two major concepts of the platform are the **assignments** (provided by teaching staff) and the **exercises** (performed by students).
More details here : [Dojo detailed presentation](0-Dojo-presentation)
## User documentation
* [Installation of the CLI](UserDocumentation/0-Installation)
* [Authentification](UserDocumentation/1-Authentification)
* [Assignment creation](UserDocumentation/2-Assignment-creation)
* [Exercice creation](UserDocumentation/3-Exercise-creation)
## Tutorials / Exemples
### Students / Everyone
* [How to perform an exercise](Tutorials/0-Exercise-perform)
### Teaching staff
* [How to create and publish an assignment](Tutorials/1-Assignment-creation)
## Development / Contribution
* [How to contribute]() - Available soon
* [How to setup your development environment](Development/1-How-to-setup-your-development-environment)
* [How to add a new command](Development/2-How-to-add-a-new-command)
\ No newline at end of file
All documentations are available on the [Dojo documentation website](https://www.hepiapp.ch/) : https://www.hepiapp.ch/.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment