diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ba99931f5..c8dfb2988 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,12 @@ on: [push, pull_request] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TEST_GITHUB_TOKEN: ${{ secrets.TEST_GITHUB_TOKEN }} + TEST_GITHUB_REPO: https://github.com/iterative/cml_qa_tests_dummy + TEST_GITHUB_SHA: 62edc8b3f46a60b3fe1e5c08fd3e0046d350ee29 + TEST_GITLAB_TOKEN: ${{ secrets.TEST_GITLAB_TOKEN }} + TEST_GITLAB_REPO: https://gitlab.com/iterative.ai/cml_qa_tests_dummy + TEST_GITLAB_SHA: c4c13286e78dc252dd2611f31a755f10d343fbd4 jobs: test_and_deploy: diff --git a/bin/cml-cloud-runner-entrypoint.js b/bin/cml-cloud-runner-entrypoint.js index 31e26d0b5..9640f9540 100755 --- a/bin/cml-cloud-runner-entrypoint.js +++ b/bin/cml-cloud-runner-entrypoint.js @@ -1,40 +1,40 @@ #!/usr/bin/env node const { spawn } = require('child_process'); + const { exec, randid } = require('../src/utils'); -const { URL } = require('url'); +const CML = require('../src/cml'); const { DOCKER_MACHINE, // DEPRECATED RUNNER_PATH, - RUNNER_REPO, RUNNER_IDLE_TIMEOUT = 5 * 60, RUNNER_LABELS = 'cml', RUNNER_NAME = randid(), RUNNER_EXECUTOR = 'shell', RUNNER_RUNTIME = '', - RUNNER_IMAGE = 'dvcorg/cml:latest' + RUNNER_IMAGE = 'dvcorg/cml:latest', + + RUNNER_DRIVER, + RUNNER_REPO, + repo_token } = process.env; -const { protocol, host, pathname } = new URL(RUNNER_REPO); +const cml = new CML({ + driver: RUNNER_DRIVER, + repo: RUNNER_REPO, + token: repo_token +}); +const IS_GITHUB = cml.driver === 'github'; +const { protocol, host } = new URL(RUNNER_REPO); const RUNNER_REPO_ORIGIN = `${protocol}//${host}`; -process.env.CI_API_V4_URL = `${RUNNER_REPO_ORIGIN}/api/v4/`; -process.env.GITHUB_REPOSITORY = process.env.CI_PROJECT_PATH = pathname.substring( - 1, - pathname.length - (pathname.endsWith('/') ? 1 : 0) -); -const IS_GITHUB = RUNNER_REPO_ORIGIN === 'https://github.com'; let TIMEOUT_TIMER = 0; let JOB_RUNNING = false; let RUNNER_TOKEN; let GITLAB_CI_TOKEN; -const { get_runner_token, register_runner } = IS_GITHUB - ? require('../src/github') - : require('../src/gitlab'); - const shutdown_docker_machine = async () => { console.log('Shutting down docker machine'); try { @@ -54,7 +54,6 @@ const shutdown_host = async () => { ); } catch (err) { console.log(`Failed destroying terraform: ${err.message}`); - // shutdown_host(); } } catch (err) { console.log(err.message); @@ -67,11 +66,7 @@ const shutdown = async (error) => { try { if (IS_GITHUB) { - console.log( - await exec( - `${RUNNER_PATH}/config.sh remove --token "${RUNNER_TOKEN}"` - ) - ); + await cml.unregister_runner({ name: RUNNER_NAME }); } else { console.log(await exec(`gitlab-runner verify --delete`)); console.log( @@ -80,7 +75,9 @@ const shutdown = async (error) => { ) ); } - } catch (err) {} + } catch (err) { + console.log(err); + } await shutdown_docker_machine(); await shutdown_host(); @@ -98,7 +95,7 @@ process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); process.on('SIGQUIT', shutdown); const run = async () => { - RUNNER_TOKEN = await get_runner_token(); + RUNNER_TOKEN = await cml.runner_token(); if (!RUNNER_TOKEN) { throw new Error( @@ -124,16 +121,16 @@ const run = async () => { command = `${RUNNER_PATH}/run.sh`; } else { console.log('Registering Gitlab runner'); - const runner = await register_runner({ + const runner = await cml.register_runner({ tags: RUNNER_LABELS, - token: RUNNER_TOKEN + runner_token: RUNNER_TOKEN }); GITLAB_CI_TOKEN = runner.token; command = `gitlab-runner --log-format="json" run-single \ - --url "${RUNNER_REPO_ORIGIN}" \ --token "${runner.token}" \ + --url "${RUNNER_REPO_ORIGIN}" \ --executor "${RUNNER_EXECUTOR}" \ --docker-runtime "${RUNNER_RUNTIME}" \ --docker-image "${RUNNER_IMAGE}" \ diff --git a/bin/cml-cloud-runner.js b/bin/cml-cloud-runner.js index 66c249d8a..73a3819b3 100644 --- a/bin/cml-cloud-runner.js +++ b/bin/cml-cloud-runner.js @@ -5,6 +5,7 @@ const NodeSSH = require('node-ssh').NodeSSH; const fss = require('fs'); const fs = fss.promises; const path = require('path'); + const { exec, sleep, @@ -12,9 +13,10 @@ const { parse_param_newline } = require('../src/utils'); -const { handle_error, repo: REPO, token: TOKEN } = process.env.GITHUB_ACTIONS - ? require('../src/github') - : require('../src/gitlab'); +const CML = require('../src/cml'); + +let REPO; +let TOKEN; const TF_FOLDER = '.cml'; const TF_NO_LOCAL = '.nolocal'; @@ -55,7 +57,8 @@ const setup_runners = async (opts) => { name: runner_name, image = 'dvcorg/cml:latest', 'rsa-private-key': rsa_private_key, - attached + attached, + driver } = opts; const tf_path = path.join(TF_FOLDER, 'main.tf'); @@ -107,6 +110,7 @@ const setup_runners = async (opts) => { -v $(pwd)/main.tf:/main.tf \ -e "repo_token=${repo_token}" \ -e "RUNNER_REPO=${runner_repo}" \ + -e "RUNNER_DRIVER=${driver}" \ ${runner_labels ? `-e "RUNNER_LABELS=${runner_labels}"` : ''} \ ${ runner_idle_timeout @@ -208,9 +212,13 @@ const shutdown = async () => { }; const run = async (opts) => { + const cml = new CML(opts); + REPO = cml.env_repo(); + TOKEN = cml.env_token(); + try { const terraform_state = await run_terraform(opts); - await setup_runners({ terraform_state, ...opts }); + await setup_runners({ terraform_state, ...opts, driver: cml.driver }); await sleep(20); } catch (err) { await destroy_terraform({}); @@ -267,5 +275,21 @@ const argv = yargs .boolean('attached') .describe('attached', 'Runs the runner in the foreground.') .coerce('rsa-private-key', parse_param_newline) + .default('repo') + .describe( + 'repo', + 'Specifies the repo to be used. If not specified is extracted from the CI ENV.' + ) + .default('token') + .describe( + 'token', + 'Personal access token to be used. If not specified in extracted from ENV repo_token or GITLAB_TOKEN.' + ) + .default('driver') + .choices('driver', ['github', 'gitlab']) + .describe('driver', 'If not specify it infers it from the ENV.') .help('h').argv; -run(argv).catch((e) => handle_error(e)); +run(argv).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/bin/cml-cloud-runner.test.js b/bin/cml-cloud-runner.test.js index 6d50216f8..a25502493 100644 --- a/bin/cml-cloud-runner.test.js +++ b/bin/cml-cloud-runner.test.js @@ -13,8 +13,8 @@ describe('CML e2e', () => { --version Show version number [boolean] --repo-token Repository token. Defaults to workflow env variable repo_token. - --repo Repository to register with. Tries to guess from workflow - env variables. + --repo Specifies the repo to be used. If not specified is + extracted from the CI ENV. --labels Comma delimited runner labels. Defaults to cml --idle-timeout Time in seconds for the runner to be waiting for jobs before shutting down. Defaults to 5 min @@ -29,6 +29,10 @@ describe('CML e2e', () => { --rsa-private-key Your private RSA SHH key. If not provided will be generated by the tf provider. [default: \\"\\"] --attached Runs the runner in the foreground. [boolean] + --token Personal access token to be used. If not specified in + extracted from ENV repo_token or GITLAB_TOKEN. + --driver If not specify it infers it from the ENV. + [choices: \\"github\\", \\"gitlab\\"] -h Show help [boolean]" `); }); diff --git a/bin/cml-publish.js b/bin/cml-publish.js index 0f0b9cbea..c89a6faa2 100644 --- a/bin/cml-publish.js +++ b/bin/cml-publish.js @@ -6,33 +6,18 @@ console.log = console.error; const fs = require('fs').promises; const pipe_args = require('../src/pipe-args'); const yargs = require('yargs'); -const { publish_file } = require('../src/report'); -const { GITHUB_ACTIONS } = process.env; - -const { handle_error } = GITHUB_ACTIONS - ? require('../src/github') - : require('../src/gitlab'); +const CML = require('../src/cml'); const run = async (opts) => { - const { data, file } = opts; - let { 'gitlab-uploads': gitlab_uploads } = opts; - const path = opts._[0]; + const { data, file, 'gitlab-uploads': gitlab_uploads } = opts; + const path = opts._[0]; let buffer; if (data) buffer = Buffer.from(data, 'binary'); - if (GITHUB_ACTIONS && gitlab_uploads) { - console.error(` - ********************************************* - * gitlab-uploads option is only for gitlab! * - * ******************************************* - `); - - gitlab_uploads = false; - } - - const output = await publish_file({ buffer, path, gitlab_uploads, ...opts }); + const cml = new CML(opts); + const output = await cml.publish({ buffer, path, gitlab_uploads, ...opts }); if (!file) print(output); else await fs.writeFile(file, output); @@ -42,6 +27,7 @@ pipe_args.load('binary'); const data = pipe_args.piped_arg(); const argv = yargs .usage(`Usage: $0 `) + .describe('md', 'Output in markdown format [title || name](url).') .boolean('md') .describe('md', 'Output in markdown format [title || name](url).') .default('title') @@ -52,12 +38,35 @@ const argv = yargs 'gitlab-uploads', 'Uses GitLab uploads instead of CML storage. Use GitLab uploads to get around CML size limitations for hosting artifacts persistently. Only available for GitLab CI.' ) + .deprecateOption('gitlab-uploads', 'Use native instead') + .boolean('native') + .describe( + 'native', + "Uses driver's native capabilities to upload assets instead of CML's backend." + ) .default('file') .describe( 'file', 'Append the output to the given file. Create it if does not exist.' ) .alias('file', 'f') + .default('repo') + .describe( + 'repo', + 'Specifies the repo to be used. If not specified is extracted from the CI ENV.' + ) + .default('token') + .describe( + 'token', + 'Personal access token to be used. If not specified in extracted from ENV repo_token or GITLAB_TOKEN.' + ) + .default('driver') + .choices('driver', ['github', 'gitlab']) + .describe('driver', 'If not specify it infers it from the ENV.') .help('h') .demand(data ? 0 : 1).argv; -run({ ...argv, data }).catch((e) => handle_error(e)); + +run({ ...argv, data }).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/bin/cml-publish.test.js b/bin/cml-publish.test.js index 66a60a8e5..c48522f42 100644 --- a/bin/cml-publish.test.js +++ b/bin/cml-publish.test.js @@ -17,9 +17,17 @@ describe('CML e2e', () => { --gitlab-uploads Uses GitLab uploads instead of CML storage. Use GitLab uploads to get around CML size limitations for hosting artifacts persistently. Only available for GitLab CI. - [boolean] + [deprecated: Use native instead] [boolean] + --native Uses driver's native capabilities to upload assets instead + of CML's backend. [boolean] --file, -f Append the output to the given file. Create it if does not exist. + --repo Specifies the repo to be used. If not specified is extracted + from the CI ENV. + --token Personal access token to be used. If not specified in + extracted from ENV repo_token or GITLAB_TOKEN. + --driver If not specify it infers it from the ENV. + [choices: \\"github\\", \\"gitlab\\"] -h Show help [boolean]" `); }); @@ -86,4 +94,14 @@ describe('CML e2e', () => { expect(fs.existsSync(file)).toBe(true); await fs.promises.unlink(file); }); + + test('cml-publish assets/test.svg in Gitlab storage', async () => { + const { TEST_GITLAB_REPO: repo, TEST_GITLAB_TOKEN: token } = process.env; + + const output = await exec( + `echo none | node ./bin/cml-publish.js --repo=${repo} --token=${token} --gitlab-uploads assets/test.svg` + ); + + expect(output.startsWith('https://')).toBe(true); + }); }); diff --git a/bin/cml-send-comment.js b/bin/cml-send-comment.js index c80f396dc..15e3864ef 100644 --- a/bin/cml-send-comment.js +++ b/bin/cml-send-comment.js @@ -5,16 +5,15 @@ console.log = console.error; const fs = require('fs').promises; const yargs = require('yargs'); -const { head_sha: HEAD_SHA, handle_error, comment } = process.env.GITHUB_ACTIONS - ? require('../src/github') - : require('../src/gitlab'); +const CML = require('../src/cml'); const run = async (opts) => { const { 'commit-sha': sha, 'head-sha': head_sha } = opts; const path = opts._[0]; const report = await fs.readFile(path, 'utf-8'); - await comment({ commit_sha: sha || head_sha || HEAD_SHA, report }); + const cml = new CML(opts); + await cml.comment_create({ report, commit_sha: sha || head_sha }); }; const argv = yargs @@ -25,8 +24,25 @@ const argv = yargs 'Commit SHA linked to this comment. Defaults to HEAD.' ) .default('head-sha') - .describe('head-sha', 'Commit SHA linked to this comment. Defaults to HEAD') + .describe('head-sha', 'Commit SHA linked to this comment. Defaults to HEAD.') .deprecateOption('head-sha', 'Use commit-sha instead') + .default('repo') + .describe( + 'repo', + 'Specifies the repo to be used. If not specified is extracted from the CI ENV.' + ) + .default('token') + .describe( + 'token', + 'Personal access token to be used. If not specified in extracted from ENV repo_token.' + ) + .default('driver') + .choices('driver', ['github', 'gitlab']) + .describe('driver', 'If not specify it infers it from the ENV.') .help('h') .demand(1).argv; -run(argv).catch((e) => handle_error(e)); + +run(argv).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/bin/cml-send-comment.test.js b/bin/cml-send-comment.test.js index 9dd737ae1..0440242ba 100644 --- a/bin/cml-send-comment.test.js +++ b/bin/cml-send-comment.test.js @@ -2,22 +2,14 @@ jest.setTimeout(200000); const { exec } = require('../src/utils'); const fs = require('fs').promises; -const { publish_file } = require('../src/report'); -describe('CML e2e', () => { - test('cml-send-comment', async () => { - const path = 'comment.md'; - const img = await publish_file({ - path: 'assets/logo.png', - md: true, - title: 'logo' - }); +describe('Comment integration tests', () => { + const path = 'comment.md'; - const report = `## Test Comment Report \n ${img}`; - - await fs.writeFile(path, report); - await exec(`node ./bin/cml-send-comment.js ${path}`); - await fs.unlink(path); + afterEach(async () => { + try { + await fs.unlink(path); + } catch (err) {} }); test('cml-send-comment -h', async () => { @@ -29,9 +21,37 @@ describe('CML e2e', () => { Options: --version Show version number [boolean] --commit-sha Commit SHA linked to this comment. Defaults to HEAD. - --head-sha Commit SHA linked to this comment. Defaults to HEAD + --head-sha Commit SHA linked to this comment. Defaults to HEAD. [deprecated: Use commit-sha instead] + --repo Specifies the repo to be used. If not specified is extracted + from the CI ENV. + --token Personal access token to be used. If not specified in extracted + from ENV repo_token. + --driver If not specify it infers it from the ENV. + [choices: \\"github\\", \\"gitlab\\"] -h Show help [boolean]" `); }); + + test('cml-send-comment to specific repo', async () => { + const { + TEST_GITHUB_REPO: repo, + TEST_GITHUB_TOKEN: token, + TEST_GITHUB_SHA: sha + } = process.env; + + const report = `## Test Comment Report specific`; + + await fs.writeFile(path, report); + await exec( + `node ./bin/cml-send-comment.js --repo=${repo} --token=${token} --commit-sha=${sha} ${path}` + ); + }); + + test('cml-send-comment to current repo', async () => { + const report = `## Test Comment`; + + await fs.writeFile(path, report); + await exec(`node ./bin/cml-send-comment.js ${path}`); + }); }); diff --git a/bin/cml-send-github-check.js b/bin/cml-send-github-check.js index 2d96fcaf6..50e187b8c 100644 --- a/bin/cml-send-github-check.js +++ b/bin/cml-send-github-check.js @@ -5,21 +5,16 @@ console.log = console.error; const fs = require('fs').promises; const yargs = require('yargs'); -const { - head_sha: HEAD_SHA, - handle_error, - create_check_report, - CHECK_TITLE -} = process.env.GITHUB_ACTIONS - ? require('../src/github') - : require('../src/gitlab'); +const CML = require('../src/cml'); +const CHECK_TITLE = 'CML Report'; const run = async (opts) => { - const { 'head-sha': head_sha = HEAD_SHA, conclusion, title } = opts; + const { 'head-sha': head_sha, conclusion, title } = opts; const path = opts._[0]; const report = await fs.readFile(path, 'utf-8'); - await create_check_report({ head_sha, report, conclusion, title }); + const cml = new CML({ ...opts, driver: 'github' }); + await cml.check_create({ head_sha, report, conclusion, title }); }; const argv = yargs @@ -40,6 +35,20 @@ const argv = yargs ]) .default('title', CHECK_TITLE) .describe('title', 'Sets title of the check.') + .default('repo') + .describe( + 'repo', + 'Specifies the repo to be used. If not specified is extracted from the CI ENV.' + ) + .default('token') + .describe( + 'token', + 'Personal access token to be used. If not specified in extracted from ENV repo_token.' + ) .help('h') .demand(1).argv; -run(argv).catch((e) => handle_error(e)); + +run(argv).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/bin/cml-send-github-check.test.js b/bin/cml-send-github-check.test.js index 837defa19..bbbcc0ccd 100644 --- a/bin/cml-send-github-check.test.js +++ b/bin/cml-send-github-check.test.js @@ -2,31 +2,25 @@ jest.setTimeout(200000); const { exec } = require('../src/utils'); const fs = require('fs').promises; -const { publish_file } = require('../src/report'); describe('CML e2e', () => { + const path = 'check.md'; + + afterEach(async () => { + try { + await fs.unlink(path); + } catch (err) {} + }); + test('cml-send-github-check', async () => { - const path = 'check.md'; - const img = await publish_file({ - path: 'assets/logo.png', - md: true, - title: 'logo' - }); - const pdf = await publish_file({ - path: 'assets/logo.pdf', - md: true, - title: 'logo' - }); - const report = `## Test Check Report \n ${img} \n ${pdf}`; + const report = `## Test Check Report`; await fs.writeFile(path, report); process.env.GITHUB_ACTIONS && (await exec(`node ./bin/cml-send-github-check.js ${path}`)); - await fs.unlink(path); }); test('cml-send-github-check failure with tile "CML neutral test"', async () => { - const path = 'check.md'; const report = `## Hi this check should be neutral`; const title = 'CML neutral test'; const conclusion = 'neutral'; @@ -36,7 +30,6 @@ describe('CML e2e', () => { (await exec( `node ./bin/cml-send-github-check.js ${path} --title "${title}" --conclusion "${conclusion}"` )); - await fs.unlink(path); }); test('cml-send-github-check -h', async () => { @@ -49,6 +42,10 @@ describe('CML e2e', () => { --version Show version number [boolean] --head-sha Commit sha where the comment will appear. Defaults to HEAD. --title Sets title of the check. [default: \\"CML Report\\"] + --repo Specifies the repo to be used. If not specified is extracted + from the CI ENV. + --token Personal access token to be used. If not specified in extracted + from ENV repo_token. -h Show help [boolean] --conclusion[choices: \\"success\\", \\"failure\\", \\"neutral\\", \\"cancelled\\", \\"skipped\\", \\"timed_out\\"] [default: Sets the conclusion status of the check.]" diff --git a/bin/cml-tensorboard-dev.js b/bin/cml-tensorboard-dev.js index 7e9bb0c21..b9134e3b4 100644 --- a/bin/cml-tensorboard-dev.js +++ b/bin/cml-tensorboard-dev.js @@ -8,11 +8,8 @@ const fs = require('fs').promises; const { spawn } = require('child_process'); const { homedir } = require('os'); const tempy = require('tempy'); -const { exec } = require('../src/utils'); -const { handle_error } = process.env.GITHUB_ACTIONS - ? require('../src/github') - : require('../src/gitlab'); +const { exec } = require('../src/utils'); const { TB_CREDENTIALS } = process.env; @@ -125,4 +122,7 @@ const argv = yargs .alias('file', 'f') .help('h').argv; -run(argv).catch((e) => handle_error(e)); +run(argv).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index 684e9bddb..7ebb6b789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3280,6 +3280,23 @@ "assert-plus": "^1.0.0" } }, + "git-up": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.2.tgz", + "integrity": "sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ==", + "requires": { + "is-ssh": "^1.3.0", + "parse-url": "^5.0.0" + } + }, + "git-url-parse": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.4.0.tgz", + "integrity": "sha512-KlIa5jvMYLjXMQXkqpFzobsyD/V2K5DRHl5OAf+6oDFPlPLxrGDVQlIdI63c4/Kt6kai4kALENSALlzTGST3GQ==", + "requires": { + "git-up": "^4.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -3927,6 +3944,14 @@ "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", "dev": true }, + "is-ssh": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.2.tgz", + "integrity": "sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ==", + "requires": { + "protocols": "^1.1.0" + } + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -5544,6 +5569,11 @@ "remove-trailing-separator": "^1.0.1" } }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -5798,6 +5828,26 @@ "error-ex": "^1.2.0" } }, + "parse-path": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.2.tgz", + "integrity": "sha512-HSqVz6iuXSiL8C1ku5Gl1Z5cwDd9Wo0q8CoffdAghP6bz8pJa1tcMC+m4N+z6VAS8QdksnIGq1TB6EgR4vPR6w==", + "requires": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "parse-url": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.2.tgz", + "integrity": "sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA==", + "requires": { + "is-ssh": "^1.3.0", + "normalize-url": "^3.3.0", + "parse-path": "^4.0.0", + "protocols": "^1.4.0" + } + }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", @@ -6007,6 +6057,11 @@ "sisteransi": "^1.0.4" } }, + "protocols": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==" + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", diff --git a/package.json b/package.json index 33700d4bd..e31d528d3 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@actions/github": "^4.0.0", "file-type": "^14.2.0", "form-data": "^3.0.0", + "git-url-parse": "^11.4.0", "is-svg": "^4.2.1", "node-fetch": "^2.6.0", "node-forge": "^0.10.0", diff --git a/src/cml.js b/src/cml.js new file mode 100644 index 000000000..b1051fdfc --- /dev/null +++ b/src/cml.js @@ -0,0 +1,107 @@ +const { execSync } = require('child_process'); +const git_url_parse = require('git-url-parse'); + +const Gitlab = require('./drivers/gitlab'); +const Github = require('./drivers/github'); +const { upload, exec } = require('./utils'); + +const uri_no_trailing_slash = (uri) => { + return uri.endsWith('/') ? uri.substr(0, uri.length - 1) : uri; +}; + +const repo_from_origin = () => { + const origin = execSync('git config --get remote.origin.url').toString( + 'utf8' + ); + return git_url_parse(origin).toString('https').replace('.git', ''); +}; + +const infer_driver = (opts = {}) => { + const { repo } = opts; + if (repo && repo.includes('github.com')) return 'github'; + if (repo && repo.includes('gitlab.com')) return 'gitlab'; + + const { GITHUB_REPOSITORY, CI_PROJECT_URL } = process.env; + if (GITHUB_REPOSITORY) return 'github'; + if (CI_PROJECT_URL) return 'gitlab'; +}; + +const get_driver = (opts) => { + const { driver, repo, token } = opts; + if (!driver) throw new Error('driver not set'); + + if (driver === 'github') return new Github({ repo, token }); + if (driver === 'gitlab') return new Gitlab({ repo, token }); + + throw new Error('driver unknown!'); +}; + +const infer_token = () => { + const { repo_token, GITHUB_TOKEN, GITLAB_TOKEN } = process.env; + return repo_token || GITHUB_TOKEN || GITLAB_TOKEN; +}; + +class CML { + constructor(opts = {}) { + const { driver, repo, token } = opts; + + this.repo = uri_no_trailing_slash(repo || repo_from_origin()); + this.token = token || infer_token(); + this.driver = driver || infer_driver({ repo: this.repo }); + } + + async head_sha() { + return (await exec(`git rev-parse HEAD`)).replace(/(\r\n|\n|\r)/gm, ''); + } + + async comment_create(opts = {}) { + const sha = await this.head_sha(); + opts.commit_sha = opts.commit_sha || sha; + + return await get_driver(this).comment_create(opts); + } + + async check_create(opts = {}) { + const sha = await this.head_sha(); + opts.head_sha = opts.head_sha || sha; + + return await get_driver(this).check_create(opts); + } + + async publish(opts = {}) { + const { title = '', md, native, gitlab_uploads } = opts; + + let mime, uri; + if (native || gitlab_uploads) { + const client = get_driver(this); + ({ mime, uri } = await client.upload(opts)); + } else { + ({ mime, uri } = await upload(opts)); + } + + if (md && mime.match('(image|video)/.*')) + return `![](${uri}${title ? ` "${title}"` : ''})`; + + if (md) return `[${title}](${uri})`; + + return uri; + } + + async runner_token() { + return await get_driver(this).runner_token(); + } + + async register_runner(opts = {}) { + return await get_driver(this).register_runner(opts); + } + + async unregister_runner(opts = {}) { + return await get_driver(this).unregister_runner(opts); + } + + log_error(e) { + console.error(e.message); + } +} + +module.exports = CML; diff --git a/src/cml.test.js b/src/cml.test.js new file mode 100644 index 000000000..06696bf7d --- /dev/null +++ b/src/cml.test.js @@ -0,0 +1,192 @@ +const CML = require('./cml'); + +describe('Github tests', () => { + const OLD_ENV = process.env; + + const { + TEST_GITHUB_TOKEN: TOKEN, + TEST_GITHUB_REPO: REPO, + TEST_GITHUB_SHA: SHA + } = process.env; + + beforeEach(() => { + jest.resetModules(); + + process.env = {}; + process.env.repo_token = TOKEN; + }); + + afterAll(() => { + process.env = OLD_ENV; + }); + + test('driver has to be github', async () => { + const cml = new CML({ repo: REPO, token: TOKEN }); + expect(cml.driver).toBe('github'); + }); + + test('Publish image without markdown returns an url', async () => { + const path = `${__dirname}/../assets/logo.png`; + + const output = await new CML().publish({ path }); + + expect(output.startsWith('https://')).toBe(true); + }); + + test('Publish image with markdown', async () => { + const path = `${__dirname}/../assets/logo.png`; + const title = 'my title'; + + const output = await new CML().publish({ path, md: true, title }); + + expect(output.startsWith('![](https://')).toBe(true); + expect(output.endsWith(` "${title}")`)).toBe(true); + }); + + test('Publish a non image file in markdown', async () => { + const path = `${__dirname}/../assets/logo.pdf`; + const title = 'my title'; + + const output = await new CML().publish({ path, md: true, title }); + + expect(output.startsWith(`[${title}](https://`)).toBe(true); + expect(output.endsWith(')')).toBe(true); + }); + + test('Comment should succeed with a valid sha', async () => { + const report = '## Test comment'; + + await new CML({ repo: REPO }).comment_create({ report, commit_sha: SHA }); + }); + + test('Comment should fail with a invalid sha', async () => { + let catched_err; + try { + const report = '## Test comment'; + const commit_sha = 'invalid_sha'; + + await new CML({ repo: REPO }).comment_create({ report, commit_sha }); + } catch (err) { + catched_err = err.message; + } + + expect(catched_err).toBe('No commit found for SHA: invalid_sha'); + }); +}); + +describe('Gitlab tests', () => { + const OLD_ENV = process.env; + + const { + TEST_GITLAB_TOKEN: TOKEN, + TEST_GITLAB_REPO: REPO, + TEST_GITLAB_SHA: SHA + } = process.env; + + beforeEach(() => { + jest.resetModules(); + + process.env = {}; + process.env.repo_token = TOKEN; + }); + + afterAll(() => { + process.env = OLD_ENV; + }); + + test('driver has to be gitlab', async () => { + const cml = new CML({ repo: REPO, token: TOKEN, driver: 'gitlab' }); + expect(cml.driver).toBe('gitlab'); + }); + + test('Publish image using gl without markdown returns an url', async () => { + const path = `${__dirname}/../assets/logo.png`; + + const output = await new CML({ repo: REPO }).publish({ + path, + gitlab_uploads: true + }); + + expect(output.startsWith('https://')).toBe(true); + }); + + test('Publish image using gl with markdown', async () => { + const path = `${__dirname}/../assets/logo.png`; + const title = 'my title'; + + const output = await new CML({ repo: REPO }).publish({ + path, + md: true, + title, + gitlab_uploads: true + }); + + expect(output.startsWith('![](https://')).toBe(true); + expect(output.endsWith(` "${title}")`)).toBe(true); + }); + + test('Publish a non image file using gl in markdown', async () => { + const path = `${__dirname}/../assets/logo.pdf`; + const title = 'my title'; + + const output = await new CML({ repo: REPO }).publish({ + path, + md: true, + title, + gitlab_uploads: true + }); + + expect(output.startsWith(`[${title}](https://`)).toBe(true); + expect(output.endsWith(')')).toBe(true); + }); + + test('Publish a non image file using native', async () => { + const path = `${__dirname}/../assets/logo.pdf`; + const title = 'my title'; + + const output = await new CML({ repo: REPO }).publish({ + path, + md: true, + title, + native: true + }); + + expect(output.startsWith(`[${title}](https://`)).toBe(true); + expect(output.endsWith(')')).toBe(true); + }); + + test('Publish should fail with an invalid driver', async () => { + let catched_err; + try { + const path = `${__dirname}/../assets/logo.pdf`; + await new CML({ repo: REPO, driver: 'invalid' }).publish({ + path, + md: true, + native: true + }); + } catch (err) { + catched_err = err.message; + } + + expect(catched_err).not.toBeUndefined(); + }); + + test('Comment should succeed with a valid sha', async () => { + const report = '## Test comment'; + await new CML({ repo: REPO }).comment_create({ report, commit_sha: SHA }); + }); + + test('Comment should fail with a invalid sha', async () => { + let catched_err; + try { + const report = '## Test comment'; + const commit_sha = 'invalid_sha'; + + await new CML({ repo: REPO }).comment_create({ report, commit_sha }); + } catch (err) { + catched_err = err.message; + } + + expect(catched_err).toBe('Not Found'); + }); +}); diff --git a/src/drivers/github.js b/src/drivers/github.js new file mode 100644 index 000000000..003b6bb1b --- /dev/null +++ b/src/drivers/github.js @@ -0,0 +1,159 @@ +const github = require('@actions/github'); + +const CHECK_TITLE = 'CML Report'; + +const owner_repo = (opts) => { + let owner, repo; + const { uri } = opts; + const { GITHUB_REPOSITORY } = process.env; + + if (uri) { + const { pathname } = new URL(uri); + [owner, repo] = pathname.substr(1).split('/'); + } else if (GITHUB_REPOSITORY) { + [owner, repo] = GITHUB_REPOSITORY.split('/'); + } + + return { owner, repo }; +}; + +const octokit = (token) => { + if (!token) throw new Error('token not found'); + + return github.getOctokit(token); +}; + +class Github { + constructor(opts = {}) { + const { repo, token } = opts; + + if (!repo) throw new Error('repo not found'); + if (!token) throw new Error('token not found'); + + this.repo = repo; + this.token = token; + } + + owner_repo(opts = {}) { + const { uri = this.repo } = opts; + return owner_repo({ uri }); + } + + async comment_create(opts = {}) { + const { report: body, commit_sha } = opts; + + const { url: commit_url } = await octokit( + this.token + ).repos.createCommitComment({ + ...owner_repo({ uri: this.repo }), + body, + commit_sha + }); + + return commit_url; + } + + async check_create(opts = {}) { + const { + report, + head_sha, + title = CHECK_TITLE, + started_at = new Date(), + completed_at = new Date(), + conclusion = 'success', + status = 'completed' + } = opts; + + const name = title; + return await octokit(this.token).checks.create({ + ...owner_repo({ uri: this.repo }), + head_sha, + started_at, + completed_at, + conclusion, + status, + name, + output: { title, summary: report } + }); + } + + async upload() { + throw new Error('Github does not support publish!'); + } + + async runner_token() { + const { owner, repo } = owner_repo({ uri: this.repo }); + const { actions } = octokit(this.token); + + if (typeof repo !== 'undefined') { + const { + data: { token } + } = await actions.createRegistrationTokenForRepo({ + owner, + repo + }); + + return token; + } + + const { + data: { token } + } = await actions.createRegistrationTokenForOrg({ + org: owner + }); + + return token; + } + + async register_runner(opts = {}) { + throw new Error('Github does not support register_runner!'); + } + + async unregister_runner(opts) { + const { name } = opts; + const { owner, repo } = owner_repo({ uri: this.repo }); + const { actions } = octokit(this.token); + const { id: runner_id } = await this.runner_by_name({ name }); + + if (typeof repo !== 'undefined') { + await actions.deleteSelfHostedRunnerFromRepo({ + owner, + repo, + runner_id + }); + } else { + await actions.deleteSelfHostedRunnerFromOrg({ + org: owner, + runner_id + }); + } + } + + async runner_by_name(opts = {}) { + const { name } = opts; + const { owner, repo } = owner_repo({ uri: this.repo }); + const { actions } = octokit(this.token); + let runners = []; + + if (typeof repo !== 'undefined') { + ({ + data: { runners } + } = await actions.listSelfHostedRunnersForRepo({ + owner, + repo, + per_page: 100 + })); + } else { + ({ + data: { runners } + } = await actions.listSelfHostedRunnersForOrg({ + org: owner, + per_page: 100 + })); + } + + return runners.filter((runner) => runner.name === name)[0]; + } +} + +module.exports = Github; diff --git a/src/drivers/github.test.js b/src/drivers/github.test.js new file mode 100644 index 000000000..87e426885 --- /dev/null +++ b/src/drivers/github.test.js @@ -0,0 +1,41 @@ +jest.setTimeout(20000); + +const GithubClient = require('./github'); + +const { + TEST_GITHUB_TOKEN: TOKEN, + TEST_GITHUB_REPO: REPO, + TEST_GITHUB_SHA: SHA +} = process.env; + +describe('Non Enviromental tests', () => { + const client = new GithubClient({ repo: REPO, token: TOKEN }); + + test('test repo and token', async () => { + expect(client.repo).toBe(REPO); + expect(client.token).toBe(TOKEN); + + const { owner, repo } = client.owner_repo(); + const parts = REPO.split('/'); + expect(owner).toBe(parts[parts.length - 2]); + expect(repo).toBe(parts[parts.length - 1]); + }); + + test('Comment', async () => { + const report = '## Test comment'; + const commit_sha = SHA; + + await client.comment_create({ report, commit_sha }); + }); + + test('Publish', async () => { + await expect(client.upload()).rejects.toThrow( + 'Github does not support publish!' + ); + }); + + test('Runner token', async () => { + const output = await client.runner_token(); + expect(output.length).toBe(29); + }); +}); diff --git a/src/drivers/gitlab.js b/src/drivers/gitlab.js new file mode 100644 index 000000000..d99d1f407 --- /dev/null +++ b/src/drivers/gitlab.js @@ -0,0 +1,96 @@ +const fetch = require('node-fetch'); +const FormData = require('form-data'); +const { URL, URLSearchParams } = require('url'); + +const { fetch_upload_data } = require('../utils'); + +class Gitlab { + constructor(opts = {}) { + const { repo, token } = opts; + + if (!token) throw new Error('token not found'); + if (!repo) throw new Error('repo not found'); + + this.token = token; + this.repo = repo; + + const { protocol, host, pathname } = new URL(this.repo); + this.repo_origin = `${protocol}//${host}`; + this.api_v4 = `${this.repo_origin}/api/v4`; + this.project_path = encodeURIComponent(pathname.substring(1)); + } + + async comment_create(opts = {}) { + const { project_path } = this; + const { commit_sha, report } = opts; + + const endpoint = `/projects/${project_path}/repository/commits/${commit_sha}/comments`; + const body = new URLSearchParams(); + body.append('note', report); + + const output = await this.request({ endpoint, method: 'POST', body }); + + return output; + } + + async check_create() { + throw new Error('Gitlab does not support check!'); + } + + async upload(opts = {}) { + const { project_path, repo } = this; + const endpoint = `/projects/${project_path}/uploads`; + const { size, mime, data } = await fetch_upload_data(opts); + const body = new FormData(); + body.append('file', data); + + const { url } = await this.request({ endpoint, method: 'POST', body }); + + return { uri: `${repo}${url}`, mime, size }; + } + + async runner_token() { + const { project_path } = this; + + const endpoint = `/projects/${project_path}`; + + const { runners_token } = await this.request({ endpoint }); + + return runners_token; + } + + async register_runner(opts = {}) { + const { tags, runner_token } = opts; + + const endpoint = `/runners`; + const body = new URLSearchParams(); + body.append('token', runner_token); + body.append('tag_list', tags); + body.append('locked', 'true'); + body.append('run_untagged', 'true'); + body.append('access_level', 'not_protected'); + + return await this.request({ endpoint, method: 'POST', body }); + } + + async unregister_runner(opts = {}) { + throw new Error('Gitlab does not support unregister_runner!'); + } + + async request(opts = {}) { + const { token, api_v4 } = this; + const { endpoint, method = 'GET', body } = opts; + + if (!endpoint) throw new Error('Gitlab API endpoint not found'); + + const headers = { 'PRIVATE-TOKEN': token, Accept: 'application/json' }; + const url = `${api_v4}${endpoint}`; + const response = await fetch(url, { method, headers, body }); + + if (response.status > 300) throw new Error(response.statusText); + + return await response.json(); + } +} + +module.exports = Gitlab; diff --git a/src/drivers/gitlab.test.js b/src/drivers/gitlab.test.js new file mode 100644 index 000000000..82b296885 --- /dev/null +++ b/src/drivers/gitlab.test.js @@ -0,0 +1,46 @@ +jest.setTimeout(20000); + +const GitlabClient = require('./gitlab'); + +const { + TEST_GITLAB_TOKEN: TOKEN, + TEST_GITLAB_REPO: REPO, + TEST_GITLAB_SHA: SHA +} = process.env; + +describe('Non Enviromental tests', () => { + const client = new GitlabClient({ repo: REPO, token: TOKEN }); + + test('test repo and token', async () => { + expect(client.repo).toBe(REPO); + expect(client.token).toBe(TOKEN); + }); + + test('Comment', async () => { + const report = '## Test comment'; + const commit_sha = SHA; + + const { created_at } = await client.comment_create({ report, commit_sha }); + + expect(created_at).not.toBeUndefined(); + }); + + test('Check', async () => { + await expect(client.check_create()).rejects.toThrow( + 'Gitlab does not support check!' + ); + }); + + test('Publish', async () => { + const path = `${__dirname}/../../assets/logo.png`; + const { uri } = await client.upload({ path }); + + expect(uri).not.toBeUndefined(); + }); + + test('Runner token', async () => { + const output = await client.runner_token(); + + expect(output.length).toBe(20); + }); +}); diff --git a/src/github.js b/src/github.js deleted file mode 100644 index 8cadb8dbc..000000000 --- a/src/github.js +++ /dev/null @@ -1,112 +0,0 @@ -const github = require('@actions/github'); - -const { - GITHUB_REPOSITORY = '', - GITHUB_HEAD_REF, - GITHUB_REF, - GITHUB_SHA, - GITHUB_EVENT_NAME, - GITHUB_TOKEN, - repo_token -} = process.env; - -const [owner, repo] = GITHUB_REPOSITORY.split('/'); -const org = owner; -const IS_PR = GITHUB_EVENT_NAME === 'pull_request'; -const REF = IS_PR ? GITHUB_HEAD_REF : GITHUB_REF; -const HEAD_SHA = GITHUB_SHA; -const USER_EMAIL = 'action@github.com'; -const USER_NAME = 'GitHub Action'; - -const TOKEN = repo_token || GITHUB_TOKEN; -const REPO = `https://github.com/${GITHUB_REPOSITORY}`; - -const octokit = github.getOctokit(TOKEN); - -const CHECK_TITLE = 'CML Report'; - -const create_check_report = async (opts) => { - const { - head_sha, - report, - title = CHECK_TITLE, - started_at = new Date(), - completed_at = new Date(), - conclusion = 'success', - status = 'completed' - } = opts; - - const name = title; - const check = await octokit.checks.create({ - owner, - repo, - head_sha, - name, - started_at, - completed_at, - conclusion, - status, - output: { title, summary: report } - }); - - return check; -}; - -const comment = async (opts) => { - const { commit_sha, report } = opts; - - await octokit.repos.createCommitComment({ - owner, - repo, - commit_sha, - body: report - }); -}; - -const get_runner_token = async () => { - if (typeof repo !== 'undefined') { - const { - data: { token } - } = await octokit.actions.createRegistrationTokenForRepo({ - owner, - repo - }); - - return token; - } - - const { - data: { token } - } = await octokit.actions.createRegistrationTokenForOrg({ - org - }); - - return token; -}; - -const register_runner = async (opts) => { - throw new Error('not yet implemented'); -}; - -const handle_error = (e) => { - console.error(e.message); - process.exit(1); -}; - -exports.is_pr = IS_PR; -exports.ref = REF; -exports.head_sha = - GITHUB_EVENT_NAME === 'pull_request' - ? github.context.payload.pull_request.head.sha - : HEAD_SHA; -exports.user_email = USER_EMAIL; -exports.user_name = USER_NAME; -exports.comment = comment; -exports.get_runner_token = get_runner_token; -exports.register_runner = register_runner; -exports.handle_error = handle_error; -exports.token = TOKEN; -exports.repo = REPO; - -exports.CHECK_TITLE = CHECK_TITLE; -exports.create_check_report = create_check_report; diff --git a/src/gitlab.js b/src/gitlab.js deleted file mode 100644 index fa34d1bc2..000000000 --- a/src/gitlab.js +++ /dev/null @@ -1,115 +0,0 @@ -const fetch = require('node-fetch'); -const FormData = require('form-data'); -const { URLSearchParams } = require('url'); - -const { fetch_upload_data } = require('./utils'); - -const { - CI_API_V4_URL, - CI_PROJECT_PATH = '', - CI_PROJECT_ID, - CI_COMMIT_REF_NAME, - CI_COMMIT_SHA, - CI_MERGE_REQUEST_ID, - CI_PROJECT_URL, - GITLAB_USER_EMAIL, - GITLAB_USER_NAME, - GITLAB_TOKEN, - repo_token -} = process.env; - -const IS_PR = CI_MERGE_REQUEST_ID; -const REF = CI_COMMIT_REF_NAME; -const HEAD_SHA = CI_COMMIT_SHA; -const USER_EMAIL = GITLAB_USER_EMAIL; -const USER_NAME = GITLAB_USER_NAME; - -const TOKEN = repo_token || GITLAB_TOKEN; -const REPO = CI_PROJECT_URL; - -const comment = async (opts) => { - const { commit_sha, report } = opts; - - const endpoint = `/projects/${CI_PROJECT_ID}/repository/commits/${commit_sha}/comments`; - - const body = new URLSearchParams(); - body.append('note', report); - - await gitlab_request({ endpoint, method: 'POST', body }); -}; - -const get_runner_token = async () => { - const endpoint = `/projects/${encodeURIComponent(CI_PROJECT_PATH)}`; - const { runners_token } = await gitlab_request({ endpoint }); - - return runners_token; -}; - -const register_runner = async (opts) => { - const endpoint = `/runners`; - - const body = new URLSearchParams(); - body.append('token', opts.token); - body.append('locked', 'true'); - body.append('run_untagged', 'true'); - body.append('access_level', 'not_protected'); - body.append('tag_list', opts.tags); - - const data = await gitlab_request({ endpoint, method: 'POST', body }); - - return data; -}; - -const upload = async (opts) => { - const endpoint = `/projects/${CI_PROJECT_ID}/uploads`; - - const { size, mime, data } = await fetch_upload_data(opts); - const body = new FormData(); - body.append('file', data); - - const { url } = await gitlab_request({ endpoint, method: 'POST', body }); - - return { uri: `${CI_PROJECT_URL}${url}`, mime, size }; -}; - -const gitlab_request = async (opts) => { - const { endpoint, method = 'GET', body } = opts; - - if (!TOKEN) throw new Error('Gitlab API token not found'); - - if (!CI_API_V4_URL) throw new Error('Gitlab API url not found'); - - if (!endpoint) throw new Error('Gitlab API endpoint not found'); - - const headers = { 'PRIVATE-TOKEN': TOKEN, Accept: 'application/json' }; - const response = await fetch(`${CI_API_V4_URL}${endpoint}`, { - method, - headers, - body - }); - const json = await response.json(); - - console.log(json); - - return json; -}; - -const handle_error = (e) => { - console.error(e.message); - process.exit(1); -}; - -exports.is_pr = IS_PR; -exports.ref = REF; -exports.head_sha = HEAD_SHA; -exports.user_email = USER_EMAIL; -exports.user_name = USER_NAME; -exports.comment = comment; -exports.get_runner_token = get_runner_token; -exports.register_runner = register_runner; -exports.upload = upload; -exports.handle_error = handle_error; -exports.token = TOKEN; -exports.repo = REPO; - -exports.CHECK_TITLE = 'CML Report'; diff --git a/src/report.js b/src/report.js deleted file mode 100644 index 5777f9d5b..000000000 --- a/src/report.js +++ /dev/null @@ -1,22 +0,0 @@ -const { upload } = require('./utils'); -const { upload: gl_upload } = require('./gitlab'); - -const publish_file = async (opts) => { - const { md = false, title = '', gitlab_uploads } = opts; - - let mime, uri; - - if (gitlab_uploads) { - ({ mime, uri } = await gl_upload(opts)); - } else { - ({ mime, uri } = await upload(opts)); - } - - if (md && mime.match('(image|video)/.*')) - return `![](${uri}${title ? ` "${title}"` : ''})`; - if (md) return `[${title}](${uri})`; - - return uri; -}; - -exports.publish_file = publish_file; diff --git a/src/report.test.js b/src/report.test.js deleted file mode 100644 index e670d1b6f..000000000 --- a/src/report.test.js +++ /dev/null @@ -1,35 +0,0 @@ -const REPORT = require('../src/report'); - -describe('Report tests', () => { - test('Publish image', async () => { - const path = './assets/logo.png'; - const md = false; - const title = 'my title'; - - const output = await REPORT.publish_file({ path, md, title }); - - expect(output.startsWith('https://')).toBe(true); - }); - - test('Publish image md', async () => { - const path = './assets/logo.png'; - const md = true; - const title = 'my title'; - - const output = await REPORT.publish_file({ path, md, title }); - - expect(output.startsWith('![](https://')).toBe(true); - expect(output.endsWith(` "${title}")`)).toBe(true); - }); - - test('Publish file md', async () => { - const path = './assets/logo.pdf'; - const md = true; - const title = 'my title'; - - const output = await REPORT.publish_file({ path, md, title }); - - expect(output.startsWith(`[${title}](https://`)).toBe(true); - expect(output.endsWith(')')).toBe(true); - }); -});