Merge pull request #1628 from dodona-edu/renovate/cachix-install-nix-β¦ #3786
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Continuous Integration & Deployment | |
on: [push] | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.ref }} | |
cancel-in-progress: true | |
permissions: | |
packages: write | |
jobs: | |
api-test: | |
name: "API π‘ - test π§ͺ" | |
runs-on: ubuntu-latest | |
env: | |
RAILS_ENV: test | |
TEST_DATABASE_URL: mysql2://root:[email protected]:3306/dolos_test | |
services: | |
mysql: | |
image: mariadb | |
env: | |
MARIADB_ROOT_PASSWORD: dolos | |
MARIADB_DATABASE: dolos_test | |
MARIADB_HOST: localhost | |
MARIADB_MYSQL_LOCALHOST_USER: 1 | |
MARIADB_MYSQL_LOCALHOST_GRANTS: USAGE | |
ports: | |
- 3306:3306 | |
options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=5 | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Ruby π | |
uses: ruby/setup-ruby@v1 | |
with: | |
working-directory: api/ | |
bundler-cache: true | |
- name: Run tests π§ͺ | |
run: | | |
cd api/ | |
docker pull ghcr.io/dodona-edu/dolos-cli:latest | |
bundle exec rails db:prepare | |
bundle exec rails test | |
api-lint: | |
name: "API π‘ - lint π" | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Ruby π | |
uses: ruby/setup-ruby@v1 | |
with: | |
working-directory: api/ | |
bundler-cache: true | |
- name: Run rubocop π | |
run: | | |
cd api/ | |
bundle exec rubocop | |
install-deps: | |
name: Install (Node ${{ matrix.node }}) β‘οΈ | |
runs-on: ubuntu-latest | |
strategy: | |
matrix: | |
node: [ "18", "20", "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Cache npm dependencies | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Setup Node π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Npm install β‘οΈ | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: npm install | |
cli-build: | |
name: "CLI π»οΈ - build π§" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "18", "20", "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Build π§ | |
run: | | |
cd cli/ | |
npm run build -- --force | |
cli-lint: | |
name: "CLI π»οΈ - lint π" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Lint π | |
run: | | |
cd cli/ | |
npm run lint | |
core-build-test: | |
name: "Core β€οΈ - build & test π§" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "18", "20", "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Build & test π§ | |
run: | | |
cd core/ | |
npm run build | |
npm run test -- -v --serial | |
core-lint: | |
name: "Core β€οΈ - lint π" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Lint π | |
run: | | |
cd core/ | |
npm run lint | |
lib-build-test: | |
name: "Lib π - build & test π§" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "18", "20", "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Build & test π§ | |
run: | | |
cd lib/ | |
npm run build | |
npm run test -- -v --serial | |
parsers-build: | |
name: "Parsers π - build π§" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "18", "20", "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Build π§ | |
run: | | |
cd parsers/ | |
npm run build | |
lib-lint: | |
name: "Lib π - lint π" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Lint π | |
run: | | |
cd lib/ | |
npm run lint | |
web-build-deploy: | |
name: "Web π - build & deploy π§" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Build core π§ | |
run: | | |
cd core/ | |
npm run build | |
- name: Build web π§ | |
run: | | |
cd web/ | |
npm run build | |
- name: Deploy to staging π | |
run: | | |
if [ -n "$KNOWN_HOSTS" ]; then | |
cd web/ | |
mkdir -p ~/.ssh | |
echo "$SSH_KEY" > ~/.ssh/id_rsa | |
chmod 600 ~/.ssh/id_rsa | |
echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts | |
chmod 600 ~/.ssh/known_hosts | |
mkdir -p "deploy/$REF" | |
cp -a dist/. "deploy/$REF" | |
rsync -glpPrtvz \ | |
--relative \ | |
--delete \ | |
-e 'ssh -p 4840' \ | |
"deploy/./$REF" \ | |
"[email protected]:web/" | |
else | |
echo "Skipping deploy because secrets are not available" | |
fi | |
env: | |
SSH_KEY: ${{ secrets.SSH_KEY }} | |
KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }} | |
REF: ${{ github.ref_name }} | |
web-lint: | |
name: "Web π - lint π" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Lint π | |
run: | | |
cd web/ | |
npm run lint | |
docs: | |
name: "Docs π" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Build π§ | |
run: | | |
cd docs/ | |
npm install | |
npm run build | |
- name: Deploy π | |
if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
run: | | |
if [ -n "$KNOWN_HOSTS" ]; then | |
cd docs/ | |
mkdir -p ~/.ssh | |
echo "$SSH_KEY" > ~/.ssh/id_rsa | |
chmod 600 ~/.ssh/id_rsa | |
echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts | |
chmod 600 ~/.ssh/known_hosts | |
rsync -glpPrtvz \ | |
--delete \ | |
-e 'ssh -p 4840' \ | |
.vitepress/dist/ \ | |
[email protected]:docs | |
else | |
echo "Skipping deploy because secrets are not available" | |
fi | |
env: | |
SSH_KEY: ${{ secrets.SSH_KEY }} | |
KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }} | |
publish: | |
name: "Publish packages on NPM π¦" | |
runs-on: ubuntu-latest | |
needs: install-deps | |
if: startsWith(github.ref, 'refs/tags/v') | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Fetch Dependencies β‘οΈ | |
id: cache-dependencies | |
uses: actions/cache@v4 | |
with: | |
path: | | |
${{ github.workspace }}/node_modules | |
${{ github.workspace }}/*/node_modules | |
${{ github.workspace }}/parsers/build | |
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('./package-lock.json', './package.json', './*/package.json', './parsers/binding.gyp', './.gitmodules') }} | |
- name: Check if cache was hit π | |
if: steps.cache-dependencies.outputs.cache-hit != 'true' | |
run: | | |
echo "Should have hit cache" | |
false | |
- name: Parse tag | |
id: parse_tag | |
run: "echo ${{ github.ref }} | sed 's#^refs/tags/#version=#' >> $GITHUB_OUTPUT" | |
- name: Draft release | |
id: create_release | |
uses: release-drafter/release-drafter@v6 | |
with: | |
name: ${{ steps.parse_tag.outputs.version }} | |
tag: ${{ steps.parse_tag.outputs.version }} | |
version: ${{ steps.parse_tag.outputs.version }} | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build core π§ | |
run: | | |
cd core/ | |
npm run build | |
- name: Build parsers π§ | |
run: | | |
cd parsers/ | |
npm run build | |
- name: Build lib π§ | |
run: | | |
cd lib/ | |
npm run build | |
- name: Build CLI π§ | |
run: | | |
cd cli/ | |
npm run build -- --force | |
- name: Build web π§ | |
run: | | |
cd web/ | |
npm run build | |
- name: Publish @dodona/dolos-core to NPM | |
uses: JS-DevTools/npm-publish@v3 | |
with: | |
token: ${{ secrets.NPM_AUTH_TOKEN }} | |
package: ./core/package.json | |
strategy: all | |
access: public | |
- name: Publish @dodona/dolos-parsers to NPM | |
uses: JS-DevTools/npm-publish@v3 | |
with: | |
token: ${{ secrets.NPM_AUTH_TOKEN }} | |
package: ./parsers/package.json | |
strategy: all | |
access: public | |
- name: Publish @dodona/dolos-lib to NPM | |
uses: JS-DevTools/npm-publish@v3 | |
with: | |
token: ${{ secrets.NPM_AUTH_TOKEN }} | |
package: ./lib/package.json | |
strategy: all | |
access: public | |
- name: Publish @dodona/dolos-web to NPM | |
uses: JS-DevTools/npm-publish@v3 | |
with: | |
token: ${{ secrets.NPM_AUTH_TOKEN }} | |
package: ./web/package.json | |
strategy: all | |
access: public | |
- name: Publish @dodona/dolos to NPM | |
uses: JS-DevTools/npm-publish@v3 | |
with: | |
token: ${{ secrets.NPM_AUTH_TOKEN }} | |
package: ./cli/package.json | |
strategy: all | |
access: public | |
demo: | |
name: "Publish demo π" | |
needs: publish | |
runs-on: ubuntu-latest | |
strategy: | |
matrix: | |
node: [ "22" ] | |
fail-fast: false | |
steps: | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Install dolos | |
run: | | |
npm install -g @dodona/dolos | |
dolos --version | |
- name: Setup SSH | |
run: | | |
mkdir -p ~/.ssh | |
echo "$SSH_KEY" > ~/.ssh/id_rsa | |
chmod 600 ~/.ssh/id_rsa | |
echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts | |
chmod 600 ~/.ssh/known_hosts | |
env: | |
SSH_KEY: ${{ secrets.SSH_KEY }} | |
KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }} | |
- name: Prepare analysis | |
run: | | |
mkdir analysis | |
cd analysis | |
rsync \ | |
-e 'ssh -p 4840' \ | |
[email protected]:demo-datasets.zip \ | |
. | |
unzip demo-datasets.zip | |
- name: Run analysis on samples | |
run: | | |
cd analysis | |
dolos -l javascript -f csv -o samples-results samples/info.csv | |
- name: Run analysis on SOCO Java | |
run: | | |
cd analysis | |
dolos -l java -f csv -o java-results soco/java-labels.csv | |
- name: Run analysis on SOCO C | |
run: | | |
cd analysis | |
dolos -l c -f csv -o c-results soco/c-labels.csv | |
- name: Run analysis on Pyramidal constants exercise | |
run: | | |
cd analysis | |
dolos -l python -f csv -o pyramidal-exercise-results "exercise - Pyramidal constants/info.csv" | |
- name: Run analysis on Pyramidal constants evaluation | |
run: | | |
cd analysis | |
dolos -l python -f csv -o pyramidal-evaluation-results "evaluation - Pyramidal constants/info.csv" | |
- name: Deploy results to demo site | |
run: | | |
cd analysis | |
npm pack @dodona/dolos-web | |
tar xzf dodona-dolos-web-*.tgz | |
mkdir -p demo/sample | |
mkdir -p demo/soco | |
mkdir -p demo/pyramidal-constants/exercise | |
mkdir -p demo/pyramidal-constants/evaluation | |
cp -r package/dist/. demo/sample | |
cp -r package/dist/. demo/soco/java | |
cp -r package/dist/. demo/soco/c | |
cp -r package/dist/. demo/pyramidal-constants/exercise | |
cp -r package/dist/. demo/pyramidal-constants/evaluation | |
cp -r samples-results demo/sample/data | |
cp -r java-results demo/soco/java/data | |
cp -r c-results demo/soco/c/data | |
cp -r pyramidal-exercise-results demo/pyramidal-constants/exercise/data | |
cp -r pyramidal-evaluation-results demo/pyramidal-constants/evaluation/data | |
rsync -glpPrtvz \ | |
-e 'ssh -p 4840' \ | |
demo/ \ | |
[email protected]:demo | |
docker: | |
name: "Publish docker containers π’" | |
needs: publish | |
runs-on: ubuntu-latest | |
permissions: | |
packages: write | |
steps: | |
- name: Checkout ποΈ | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
- name: Setup Node π | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ matrix.node }} | |
- name: Parse tag (without v) | |
id: parse_tag | |
run: "echo ${{ github.ref }} | sed 's#^refs/tags/v#version=#' >> $GITHUB_OUTPUT" | |
- name: Build the Dolos images | |
run: | | |
docker build docker/ -t ghcr.io/dodona-edu/dolos-cli:${{ steps.parse_tag.outputs.version }} | |
docker build web/ -t ghcr.io/dodona-edu/dolos-web:${{ steps.parse_tag.outputs.version }} | |
docker build api/ -t ghcr.io/dodona-edu/dolos-api:${{ steps.parse_tag.outputs.version }} | |
- name: Login to the container registry | |
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin | |
- name: Tag the new images with latest | |
run: | | |
docker tag ghcr.io/dodona-edu/dolos-cli:${{ steps.parse_tag.outputs.version }} ghcr.io/dodona-edu/dolos-cli:latest | |
docker tag ghcr.io/dodona-edu/dolos-web:${{ steps.parse_tag.outputs.version }} ghcr.io/dodona-edu/dolos-web:latest | |
docker tag ghcr.io/dodona-edu/dolos-api:${{ steps.parse_tag.outputs.version }} ghcr.io/dodona-edu/dolos-api:latest | |
- name: Push the images | |
run: | | |
docker push --all-tags ghcr.io/dodona-edu/dolos-cli | |
docker push --all-tags ghcr.io/dodona-edu/dolos-web | |
docker push --all-tags ghcr.io/dodona-edu/dolos-api |