diff --git a/Dockerfile_ExerciceChecker b/Dockerfile_ExerciceChecker index fb8bf1bfba21f5847f671b997b421a0c4ca42b1a..4ce2e0829fdd0c046e653391b2c7fa818970106f 100644 --- a/Dockerfile_ExerciceChecker +++ b/Dockerfile_ExerciceChecker @@ -31,6 +31,9 @@ FROM docker:latest LABEL maintainer="Michaël Minelli <michael-jean.minelli@hesge.ch>" LABEL Description="Dojo exercice checker - Container generation for usage in exercice pipelines" +RUN apk update +RUN apk add git + ARG BUILD_WORKDIR COPY --from=builder ${BUILD_WORKDIR}/bin/app /usr/local/bin/dojo_exercice_checker \ No newline at end of file diff --git a/ExerciceChecker/.idea/vcs.xml b/ExerciceChecker/.idea/vcs.xml index 6c0b8635858dc7ad44b93df54b762707ce49eefc..d86e73b516141a07b5ba1f16f6f315749512bedc 100644 --- a/ExerciceChecker/.idea/vcs.xml +++ b/ExerciceChecker/.idea/vcs.xml @@ -2,5 +2,7 @@ <project version="4"> <component name="VcsDirectoryMappings"> <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> + <mapping directory="$PROJECT_DIR$/src/shared" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" /> </component> </project> \ No newline at end of file diff --git a/ExerciceChecker/assets/docker-compose-override.yml b/ExerciceChecker/assets/docker-compose-override.yml new file mode 100644 index 0000000000000000000000000000000000000000..4f2e6cf0c92cb979143a91d8ebb0fcf6e2294444 --- /dev/null +++ b/ExerciceChecker/assets/docker-compose-override.yml @@ -0,0 +1,6 @@ +volumes: + {{VOLUME_NAME}}: + driver_opts: + type : none + device: {{MOUNT_PATH}} + o : bind diff --git a/ExerciceChecker/package-lock.json b/ExerciceChecker/package-lock.json index 955f5c55c38f31e439e87be0b6d2f20c6d5442cc..d27a3443c703494fa7d31249914e4ed23421a1b3 100644 --- a/ExerciceChecker/package-lock.json +++ b/ExerciceChecker/package-lock.json @@ -8,18 +8,28 @@ "name": "dojo_exercice_checker", "version": "1.0.0", "dependencies": { + "ajv": "^8.12.0", "axios": "^1.4.0", + "boxen": "^5.1.2", "chalk": "^4.1.2", + "dockerode": "^3.3.5", "dotenv": "^16.3.1", + "fs-extra": "^11.1.1", "http-status-codes": "^2.2.0", + "json5": "^2.2.3", "ora": "^5.4.1", + "tar-stream": "^3.1.6", "winston": "^3.10.0" }, "bin": { "dirmanager": "dist/app.js" }, "devDependencies": { + "@types/dockerode": "^3.3.19", + "@types/fs-extra": "^11.0.1", + "@types/js-yaml": "^4.0.5", "@types/node": "^18.17.1", + "@types/tar-stream": "^2.2.2", "pkg": "^5.8.1", "ts-node": "^10.9.1", "typescript": "^5.1.6" @@ -83,6 +93,11 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -236,12 +251,75 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/docker-modem": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.2.tgz", + "integrity": "sha512-qC7prjoEYR2QEe6SmCVfB1x3rfcQtUr1n4x89+3e0wSTMQ/KYCyf+/RAA9n2tllkkNc6//JMUZePdFRiGIWfaQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.19.tgz", + "integrity": "sha512-7CC5yIpQi+bHXwDK43b/deYXteP3Lem9gdocVVHJPSRJJLMfbiOchQV3rDmAPkMw+n3GIVj7m1six3JW+VcwwA==", + "dev": true, + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", + "dev": true, + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "node_modules/@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", "dev": true }, + "node_modules/@types/ssh2": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.13.tgz", + "integrity": "sha512-08WbG68HvQ2YVi74n2iSUnYHYpUdFc/s2IsI0BHBdJwaqYJpWlVv9elL0tYShTv60yr0ObdxJR5NrCRiGJ/0CQ==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/tar-stream": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.2.tgz", + "integrity": "sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", @@ -280,6 +358,29 @@ "node": ">= 6.0.0" } }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -317,6 +418,14 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -346,6 +455,11 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -365,6 +479,14 @@ } ] }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -375,6 +497,27 @@ "readable-stream": "^3.4.0" } }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -410,6 +553,26 @@ "ieee754": "^1.1.13" } }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -428,8 +591,18 @@ "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/cli-cursor": { "version": "3.1.0", @@ -545,6 +718,20 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cpu-features": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.8.tgz", + "integrity": "sha512-BbHBvtYhUhksqTjr6bhNOjGgMnhwhGTQmOoZGD+K7BCaQDCuZl/Ve1ZxUSMRwVC4D/rkCPQ2MAIeYzrWyK7eEg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.17.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -555,7 +742,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -641,6 +827,59 @@ "node": ">=8" } }, + "node_modules/docker-modem": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", + "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.11.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", + "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^3.0.0", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode/node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/dockerode/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -655,8 +894,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enabled": { "version": "2.0.0", @@ -667,7 +905,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -690,6 +927,16 @@ "node": ">=6" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-fifo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz", + "integrity": "sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==" + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -812,22 +1059,19 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dependencies": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.14" } }, "node_modules/function-bind": { @@ -886,8 +1130,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has": { "version": "1.0.3", @@ -1012,7 +1255,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -1086,11 +1328,26 @@ "node": ">=4" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1222,8 +1479,7 @@ "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "node_modules/ms": { "version": "2.1.2", @@ -1254,6 +1510,12 @@ "readable-stream": "^3.6.0" } }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "optional": true + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -1296,7 +1558,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1433,6 +1694,36 @@ "pkg-fetch": "lib-es5/bin.js" } }, + "node_modules/pkg-fetch/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -1483,12 +1774,19 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1509,6 +1807,11 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1546,6 +1849,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -1647,6 +1958,11 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -1729,6 +2045,28 @@ "node": ">=8" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" + }, + "node_modules/ssh2": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.14.0.tgz", + "integrity": "sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.8", + "nan": "^2.17.0" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1776,6 +2114,15 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1788,7 +2135,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1853,7 +2199,7 @@ "tar-stream": "^2.1.4" } }, - "node_modules/tar-stream": { + "node_modules/tar-fs/node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", @@ -1869,6 +2215,16 @@ "node": ">=6" } }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -1964,6 +2320,22 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", @@ -1981,11 +2353,18 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { "node": ">= 10.0.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2021,6 +2400,17 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/winston": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", @@ -2059,7 +2449,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -2075,8 +2464,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/y18n": { "version": "5.0.8", diff --git a/ExerciceChecker/package.json b/ExerciceChecker/package.json index de22b9cff0f10fc06b3f281ca913857a8fef376a..1b15b659501c90fede5408afdb8a567154f57759 100644 --- a/ExerciceChecker/package.json +++ b/ExerciceChecker/package.json @@ -1,6 +1,6 @@ { "name" : "dojo_exercice_checker", - "version" : "0.0.1", + "version" : "1.0.0", "main" : "dist/app.js", "bin" : { "dirmanager": "./dist/app.js" @@ -9,7 +9,8 @@ "scripts": [], "assets" : [ "node_modules/axios/dist/node/axios.cjs", - ".env" + ".env", + "assets/**/*" ], "targets": [ "node18-linux-arm64", @@ -22,17 +23,27 @@ "test" : "echo \"Error: no test specified\" && exit 1" }, "dependencies" : { + "ajv" : "^8.12.0", "axios" : "^1.4.0", + "boxen" : "^5.1.2", "chalk" : "^4.1.2", + "dockerode" : "^3.3.5", "dotenv" : "^16.3.1", + "fs-extra" : "^11.1.1", "http-status-codes": "^2.2.0", + "json5" : "^2.2.3", "ora" : "^5.4.1", + "tar-stream" : "^3.1.6", "winston" : "^3.10.0" }, "devDependencies": { - "@types/node" : "^18.17.1", - "pkg" : "^5.8.1", - "ts-node" : "^10.9.1", - "typescript" : "^5.1.6" + "@types/dockerode" : "^3.3.19", + "@types/fs-extra" : "^11.0.1", + "@types/js-yaml" : "^4.0.5", + "@types/node" : "^18.17.1", + "@types/tar-stream": "^2.2.2", + "pkg" : "^5.8.1", + "ts-node" : "^10.9.1", + "typescript" : "^5.1.6" } } diff --git a/ExerciceChecker/src/app.ts b/ExerciceChecker/src/app.ts index a4f2ffd64f0abf492d16f4dc0565f872fb1991db..afc0cb62d547d4edeef34c18e72cace834e7b359 100644 --- a/ExerciceChecker/src/app.ts +++ b/ExerciceChecker/src/app.ts @@ -4,20 +4,173 @@ const path = require('node:path'); require('dotenv').config({ path: path.join(__dirname, '../.env') }); require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file -import chalk from 'chalk'; -import HttpManager from './managers/HttpManager'; +import Styles from './types/Styles'; +import Icon from './types/Icon'; +import boxen from 'boxen'; +import RecursiveFilesStats from './shared/helpers/recursiveFilesStats/RecursiveFilesStats'; +import Toolbox from './shared/helpers/Toolbox'; +import ExerciceHelper from './shared/helpers/ExerciceHelper'; +import ExerciceCheckerError from './types/ExerciceCheckerError'; +import { exec, spawn } from 'child_process'; +import util from 'util'; +import fs from 'fs-extra'; +import HttpManager from './managers/HttpManager'; +import DojoBackendManager from './managers/DojoBackendManager'; +import Config from './config/Config'; +import ArchiveHelper from './shared/helpers/ArchiveHelper'; -HttpManager.registerAxiosInterceptor(); +(async () => { + const execAsync = util.promisify(exec); + HttpManager.registerAxiosInterceptor(); -console.log(chalk.blue('Dojo Exercice Checker')); + console.log(Styles.APP_NAME(Config.appName)); + /* + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1 & 2: + - Read the dojo enonce file from the enonce repository + - Download immutables files (maybe throw or show an error if the files have been modified ?) + */ + console.log(Styles.INFO(`${ Icon.INFO }️ Checking the exercice's enonce and his immutable files`)); + const exerciceEnonce = await DojoBackendManager.getExerciceEnonce(); + if ( !exerciceEnonce ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercice's enonce`)); + process.exit(ExerciceCheckerError.EXERCICE_ENONCE_GET_ERROR); + } -// Step 1: Read the dojo enonce file from the enonce repository -// Step 2: Download immutables files (maybe throw or show an error if the files have been modified ?) - Can be merged with step 1 -// Step 3: Run docker-compose file -// Step 4: Wait the end of the execution of the result container -// Step 5: Get the result from the volume -// Step 6: Check content requirements and content size -// Step 7: Upload and show the results \ No newline at end of file + exerciceEnonce.immutable.forEach(immutableFile => { + const filePath = path.join(Config.folders.project, immutableFile.file_path); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, immutableFile.content, { encoding: 'base64' }); + }); + + + /* + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3 & 4 & 5: + - Get override of docker-compose file (for override the volume by a bind mount to the results folder shared between dind and the host) + - Run docker-compose file + - Get logs from linked services + */ + console.log(Styles.INFO(`${ Icon.INFO } Run docker compose file`)); + const dockerComposeOverride = fs.readFileSync(path.join(__dirname, '../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', exerciceEnonce.enonceFile.result.volume).replace('{{MOUNT_PATH}}', Config.folders.resultsExercice); + fs.writeFileSync(`${ Config.folders.project }/docker-compose-override.yml`, dockerComposeOverride); + + const changeDirectoryCommand = `cd "${ Config.folders.project }"`; + const dockerComposeCommand = `docker compose --project-name ${ Config.dockerCompose.projectName } --progress plain --file docker-compose.yml --file docker-compose-override.yml`; + + const containerExitStatus = await new Promise<[ number, string ]>((resolve) => { + let logs = '####################################################### Docker Compose & Main Container Logs #######################################################\n'; + + const dockerCompose = spawn(`${ dockerComposeCommand } run --build ${ exerciceEnonce.enonceFile.result.container }`, { + cwd : Config.folders.project, + shell: true, + env : { + 'DOCKER_BUILDKIT' : '1', + 'BUILDKIT_PROGRESS': 'plain', ...process.env + } + }); + + dockerCompose.stdout.on('data', (data) => { + logs += data.toString(); + console.log(data.toString()); + }); + + dockerCompose.stderr.on('data', (data) => { + logs += data.toString(); + console.error(data.toString()); + }); + + dockerCompose.on('exit', (code) => { + logs += '####################################################### Other Services Logs #######################################################\n'; + resolve([ code ?? ExerciceCheckerError.DOCKER_COMPOSE_UP_ERROR, logs ]); + }); + }); + const containerExitCode = containerExitStatus[0]; + if ( containerExitCode === ExerciceCheckerError.DOCKER_COMPOSE_UP_ERROR ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while running the docker compose file`)); + process.exit(containerExitCode); + } + fs.writeFileSync(`${ Config.folders.resultsDojo }/dockerComposeLogs.txt`, containerExitStatus[1]); + + console.log(Styles.INFO(`${ Icon.INFO } Acquire logs of linked services`)); + try { + await execAsync(`${ changeDirectoryCommand };${ dockerComposeCommand } logs --timestamps >> ${ Config.folders.resultsDojo }/dockerComposeLogs.txt`); + } catch ( error ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the linked services logs`)); + process.exit(ExerciceCheckerError.DOCKER_COMPOSE_LOGS_ERROR); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 6: Check content requirements and content size + console.log(Styles.INFO(`${ Icon.INFO } Validating results folder size`)); + const resultsFolderSize = await Toolbox.fs.getTotalSize(Config.folders.resultsExercice); + if ( resultsFolderSize > Config.resultsFolderMaxSizeInBytes ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Results folder size is too big (bigger than ${ Config.resultsFolderMaxSizeInBytes / 1000000 })`)); + process.exit(ExerciceCheckerError.EXERCICE_RESULTS_FOLDER_TOO_BIG); + } + + console.log(Styles.INFO(`${ Icon.INFO } Checking results file`)); + const resultsFileOriginPath = path.join(Config.folders.resultsExercice, Config.filenames.results); + const resultsFilePath = path.join(Config.folders.resultsDojo, Config.filenames.results); + + if ( !fs.existsSync(resultsFileOriginPath) ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Results file not found.`)); + process.exit(ExerciceCheckerError.EXERCICE_RESULTS_FILE_NOT_FOUND); + } + + fs.moveSync(resultsFileOriginPath, resultsFilePath, { overwrite: true }); + + const validationResults = ExerciceHelper.validateResultFile(resultsFilePath); + + if ( !validationResults.isValid ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Results file is not valid. Here are the errors :`)); + console.error(Styles.ERROR(JSON.stringify(validationResults.errors))); + process.exit(ExerciceCheckerError.EXERCICE_RESULTS_FILE_SCHEMA_NOT_VALID); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 7: Upload and show the results + try { + console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`)); + const commit: any = {}; + Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => { + commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key]; + }); + + const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, { + replacePathByRelativeOne: true, + liteStats : true + }); + + await DojoBackendManager.sendResults(containerExitCode, commit, validationResults.results!, files, await ArchiveHelper.getBase64(Config.folders.resultsVolume)); + } catch ( error ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`)); + console.error(JSON.stringify(error)); + process.exit(ExerciceCheckerError.UPLOAD); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 8: Exit with container exit code + const finalLogGlobalResult = `${ Styles.INFO('Global result') } : ${ validationResults.results!.success ? Styles.SUCCESS(`${ Icon.SUCCESS } Success`) : Styles.FAILURE(`${ Icon.FAILURE } Failure`) }`; + + const finalLogExecutionExitCode = `${ Styles.INFO('Execution exit code') } : ${ (containerExitCode == 0 ? Styles.SUCCESS : Styles.ERROR)(containerExitCode) }`; + + const finalLogResultNumbers = validationResults.results!.successfulTests || validationResults.results!.failedTests ? `\n\n${ Styles.SUCCESS('Tests passed') } : ${ validationResults.results!.successfulTests ?? '--' }\n${ Styles.ERROR('Tests failed') } : ${ validationResults.results!.failedTests ?? '--' }` : ''; + + const finalLogSuccessResultDetails = (validationResults.results!.successfulTestsList ?? []).map(testName => `- ${ Icon.SUCCESS } ${ testName }`).join('\n'); + const finalLogFailedResultDetails = (validationResults.results!.failedTestsList ?? []).map(testName => `- ${ Icon.FAILURE } ${ testName }`).join('\n'); + const finalLogResultDetails = validationResults.results!.successfulTestsList || validationResults.results!.failedTestsList ? `\n\n${ Styles.INFO('Tests') } :${ finalLogSuccessResultDetails != '' ? '\n' + finalLogSuccessResultDetails : '' }${ finalLogFailedResultDetails != '' ? '\n' + finalLogFailedResultDetails : '' }` : ''; + + console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }`, { + title : 'Results', + titleAlignment: 'center', + borderColor : 'yellow', + borderStyle : 'bold', + margin : 1, + padding : 1, + textAlignment : 'left' + })); + + process.exit(containerExitCode); +})(); \ No newline at end of file diff --git a/ExerciceChecker/src/config/Config.ts b/ExerciceChecker/src/config/Config.ts new file mode 100644 index 0000000000000000000000000000000000000000..6782c1447cc3451953505896560565b424fda141 --- /dev/null +++ b/ExerciceChecker/src/config/Config.ts @@ -0,0 +1,61 @@ +import fs from 'fs-extra'; +import path from 'path'; + + +class Config { + public readonly appName: string; + + public readonly resultsFolderMaxSizeInBytes: number; + + public readonly folders: { + project: string; resultsVolume: string; resultsDojo: string; resultsExercice: string; + }; + + public readonly filenames: { + results: string; + }; + + public readonly exercice: { + id: string; secret: string; + }; + + public readonly dockerCompose: { + projectName: string + }; + + constructor() { + this.appName = process.env.APP_NAME || ''; + + this.resultsFolderMaxSizeInBytes = Number(process.env.RESULTS_FOLDER_MAX_SIZE_IN_BYTES || 0); + + this.folders = { + project : process.env.FILES_FOLDER?.convertWithEnvVars() ?? './', + resultsVolume : process.env.RESULTS_VOLUME?.convertWithEnvVars() ?? '', + resultsDojo : path.join(process.env.RESULTS_VOLUME?.convertWithEnvVars() ?? '', 'Dojo/'), + resultsExercice: path.join(process.env.RESULTS_VOLUME?.convertWithEnvVars() ?? '', 'Exercice/') + }; + this.resetResultsVolume(); + + this.filenames = { + results: process.env.RESULTS_FILENAME || '' + }; + + this.exercice = { + id : process.env.DOJO_EXERCICE_ID || '', + secret: process.env.DOJO_SECRET || '' + }; + + this.dockerCompose = { + projectName: process.env.DOCKER_COMPOSE_PROJECT_NAME || '' + }; + } + + private resetResultsVolume(): void { + fs.emptyDirSync(this.folders.resultsVolume); + fs.emptyDirSync(this.folders.resultsDojo); + fs.emptyDirSync(this.folders.resultsExercice); + } +} + + +export default new Config(); diff --git a/ExerciceChecker/src/managers/DojoBackendManager.ts b/ExerciceChecker/src/managers/DojoBackendManager.ts index d64a25881c49bf67ddccb315ff2a08ba42033875..c1cb4c6df7f4f4d26023acd14ef1a7fe22245142 100644 --- a/ExerciceChecker/src/managers/DojoBackendManager.ts +++ b/ExerciceChecker/src/managers/DojoBackendManager.ts @@ -1,11 +1,38 @@ import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; import ApiRoutes from '../sharedByClients/types/ApiRoutes'; +import axios from 'axios'; +import DojoResponse from '../shared/types/Dojo/DojoResponse'; +import ExerciceEnonce from '../sharedByClients/models/ExerciceEnonce'; +import Config from '../config/Config'; +import ExerciceResultsFile from '../shared/types/Dojo/ExerciceResultsFile'; class DojoBackendManager { public getApiUrl(route: ApiRoutes): string { return `${ ClientsSharedConfig.apiURL }${ route }`; } + + public async getExerciceEnonce(): Promise<ExerciceEnonce | undefined> { + try { + return (await axios.get<DojoResponse<ExerciceEnonce>>(this.getApiUrl(ApiRoutes.EXERCICE_ENONCE).replace('{{id}}', Config.exercice.id))).data.data; + } catch ( error ) { + return undefined; + } + } + + public async sendResults(exitCode: number, commit: any, results: ExerciceResultsFile, files: any, archiveBase64: string): Promise<void> { + try { + await axios.post(this.getApiUrl(ApiRoutes.EXERCICE_RESULTS).replace('{{id}}', Config.exercice.id), { + exitCode : exitCode, + commit : JSON.stringify(commit), + results : JSON.stringify(results), + files : JSON.stringify(files), + archiveBase64: archiveBase64 + }); + } catch ( error ) { + throw error; + } + } } diff --git a/ExerciceChecker/src/managers/HttpManager.ts b/ExerciceChecker/src/managers/HttpManager.ts index 9cf17d86d269c27be5a92a164b68777acf269ac9..432e456dd35231502321f8f4d11b410f3dc1f0ef 100644 --- a/ExerciceChecker/src/managers/HttpManager.ts +++ b/ExerciceChecker/src/managers/HttpManager.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestHeaders } from 'axios'; import FormData from 'form-data'; import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; +import Config from '../config/Config'; class HttpManager { @@ -18,9 +19,13 @@ class HttpManager { } if ( config.url && (config.url.indexOf(ClientsSharedConfig.apiURL) !== -1) ) { + config.headers['Accept-Encoding'] = 'gzip'; + if ( config.data && Object.keys(config.data).length > 0 ) { config.headers['Content-Type'] = 'multipart/form-data'; } + + config.headers.Authorization = `ExerciceSecret ${ Config.exercice.secret }`; } return config; diff --git a/ExerciceChecker/src/shared b/ExerciceChecker/src/shared index c9154d42dac81311cf1957f0d75f806737849b40..eab5c0a5a32079fcb439a1ad79453611c8605536 160000 --- a/ExerciceChecker/src/shared +++ b/ExerciceChecker/src/shared @@ -1 +1 @@ -Subproject commit c9154d42dac81311cf1957f0d75f806737849b40 +Subproject commit eab5c0a5a32079fcb439a1ad79453611c8605536 diff --git a/ExerciceChecker/src/sharedByClients b/ExerciceChecker/src/sharedByClients index 8fe8e9417a527cf2182a9acc440e68b99024487e..c0f105590a4332ce4d6eff046324e537e769f756 160000 --- a/ExerciceChecker/src/sharedByClients +++ b/ExerciceChecker/src/sharedByClients @@ -1 +1 @@ -Subproject commit 8fe8e9417a527cf2182a9acc440e68b99024487e +Subproject commit c0f105590a4332ce4d6eff046324e537e769f756 diff --git a/ExerciceChecker/src/types/ExerciceCheckerError.ts b/ExerciceChecker/src/types/ExerciceCheckerError.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bb09d5c99b395c073ee2f7c61727de1ea910ea3 --- /dev/null +++ b/ExerciceChecker/src/types/ExerciceCheckerError.ts @@ -0,0 +1,12 @@ +enum ExerciceCheckerError { + EXERCICE_ENONCE_GET_ERROR = 200, + DOCKER_COMPOSE_UP_ERROR = 201, + DOCKER_COMPOSE_LOGS_ERROR = 202, + EXERCICE_RESULTS_FOLDER_TOO_BIG = 203, + EXERCICE_RESULTS_FILE_NOT_FOUND = 204, + EXERCICE_RESULTS_FILE_SCHEMA_NOT_VALID = 205, + UPLOAD = 206 +} + + +export default ExerciceCheckerError; \ No newline at end of file diff --git a/ExerciceChecker/src/types/Icon.ts b/ExerciceChecker/src/types/Icon.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d5a0ec79d20073891288ac585615e8ead980132 --- /dev/null +++ b/ExerciceChecker/src/types/Icon.ts @@ -0,0 +1,9 @@ +enum Icon { + INFO = 'ℹ️', + ERROR = '⛔️', + SUCCESS = '✅', + FAILURE = '❌' +} + + +export default Icon; \ No newline at end of file diff --git a/ExerciceChecker/src/types/Styles.ts b/ExerciceChecker/src/types/Styles.ts new file mode 100644 index 0000000000000000000000000000000000000000..64f6bdd5ea6ce4b1481560d600582021de114744 --- /dev/null +++ b/ExerciceChecker/src/types/Styles.ts @@ -0,0 +1,13 @@ +import chalk from 'chalk'; + + +class Styles { + public readonly APP_NAME = chalk.bgBlue.black.bold; + public readonly INFO = chalk.blue; + public readonly ERROR = chalk.red; + public readonly SUCCESS = chalk.green; + public readonly FAILURE = chalk.red; +} + + +export default new Styles(); \ No newline at end of file diff --git a/ExerciceChecker/tsconfig.json b/ExerciceChecker/tsconfig.json index d7ed4058faf2c72d3e112f4a0b4378e8a0ee1235..88b6af04266eaeba378778af03b22e9473448127 100644 --- a/ExerciceChecker/tsconfig.json +++ b/ExerciceChecker/tsconfig.json @@ -1,16 +1,16 @@ { "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "strict": true, - "target": "es6", - "module": "commonjs", - "sourceMap": true, - "esModuleInterop": true, + "rootDir" : "src", + "outDir" : "dist", + "strict" : true, + "target" : "es6", + "module" : "commonjs", + "sourceMap" : true, + "esModuleInterop" : true, "moduleResolution": "node", - "noImplicitAny": true + "noImplicitAny" : true }, - "exclude": [ + "exclude" : [ "node_modules" ] } \ No newline at end of file