Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor GitLab integration #1116

Merged
merged 29 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
88a5619
Add Gtilab functionalities
alessandrozago Jul 31, 2024
8672ba9
Fix new gitlab files using eslint
alessandrozago Aug 12, 2024
5dbe093
Add tests for GitLab class and format fixes
alessandrozago Sep 6, 2024
b67e361
Remove existing GitLab repository in the config
alessandrozago Sep 12, 2024
46567ed
Add EUPL-1.2 copyright
alessandrozago Oct 8, 2024
3a64bf0
Use only a single template for README
alessandrozago Oct 9, 2024
d46c68c
Fix release zip upload not working
alessandrozago Oct 21, 2024
e423dd3
Update changelog
alessandrozago Oct 21, 2024
61b20a6
Merge branch 'main' into main
alessandrozago Oct 22, 2024
3b964ca
Fix typo on changelog
alessandrozago Oct 22, 2024
07aac6f
Remove explicit EUPL-1.2 copyright in files
alessandrozago Oct 29, 2024
bbf5e58
Align logging messages format with latest releases
alessandrozago Oct 29, 2024
00a3076
Merge branch 'main' into main
alessandrozago Oct 29, 2024
bb72636
Merge branch 'main' into main
alessandrozago Nov 5, 2024
3b92517
Factorize code between Gitlab and GitHub reporters
Ndpnt Nov 5, 2024
48ad093
Factorize code between Gitlab and GitHub publisher
Ndpnt Nov 5, 2024
ca58f97
Fix deprecated method
Ndpnt Nov 5, 2024
a6d8d29
Improve changelog
Ndpnt Nov 5, 2024
64d1060
Improve changelog entry
Ndpnt Nov 7, 2024
8769d0f
Add release funders
Ndpnt Nov 7, 2024
aed17f3
Clarify token precedence between GitHub and GitLab
Ndpnt Nov 7, 2024
cd411eb
Update changelog entry
Ndpnt Nov 12, 2024
c9fa05e
Add backward compatibility for legacy config
Ndpnt Nov 12, 2024
4496f52
Use proper configuration key
Ndpnt Nov 12, 2024
24875eb
Improve test maintainability
Ndpnt Nov 13, 2024
0bdd637
Improve comment
Ndpnt Nov 13, 2024
356a8b8
Implement no-op clearCache in GitLab reporter
Ndpnt Nov 13, 2024
2dda758
Remove obsolete log
Ndpnt Nov 13, 2024
7df61b6
Improve wording
Ndpnt Nov 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
OTA_ENGINE_SENDINBLUE_API_KEY='xkeysib-3f51c…'
OTA_ENGINE_SMTP_PASSWORD='password'

# If both GitHub and GitLab tokens are defined, GitHub takes precedence for dataset publishing
OTA_ENGINE_GITHUB_TOKEN=ghp_XXXXXXXXX

OTA_ENGINE_GITLAB_TOKEN=XXXXXXXXXX
OTA_ENGINE_GITLAB_RELEASES_TOKEN=XXXXXXXXXX
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All changes that impact users of this module are documented in this file, in the [Common Changelog](https://common-changelog.org) format with some additional specifications defined in the CONTRIBUTING file. This codebase adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased [minor]

MattiSG marked this conversation as resolved.
Show resolved Hide resolved
> Development of this release was supported by the [European Union](https://commission.europa.eu/) and the [French Ministry for Foreign Affairs](https://www.diplomatie.gouv.fr/fr/politique-etrangere-de-la-france/diplomatie-numerique/) through its ministerial [State Startups incubator](https://beta.gouv.fr/startups/open-terms-archive.html) under the aegis of the Ambassador for Digital Affairs.

### Added

- Add support for GitLab for issue reporting
- Add support for GitLab Releases for publishing datasets

## 2.5.0 - 2024-10-29

_Full changeset and discussions: [#1115](https://github.com/OpenTermsArchive/engine/pull/1115)._
Expand Down
30 changes: 19 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions scripts/dataset/publish/github/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import fsApi from 'fs';
import path from 'path';
import url from 'url';

import config from 'config';
import { Octokit } from 'octokit';

import * as readme from '../../assets/README.template.js';

export default async function publish({ archivePath, releaseDate, stats }) {
const octokit = new Octokit({ auth: process.env.OTA_ENGINE_GITHUB_TOKEN });

const [ owner, repo ] = url.parse(config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL')).pathname.split('/').filter(component => component);

const tagName = `${path.basename(archivePath, path.extname(archivePath))}`; // use archive filename as Git tag

const { data: { upload_url: uploadUrl, html_url: releaseUrl } } = await octokit.rest.repos.createRelease({
owner,
repo,
tag_name: tagName,
name: readme.title({ releaseDate }),
body: readme.body(stats),
});

await octokit.rest.repos.uploadReleaseAsset({
data: fsApi.readFileSync(archivePath),
headers: {
'content-type': 'application/zip',
'content-length': fsApi.statSync(archivePath).size,
},
name: path.basename(archivePath),
url: uploadUrl,
});

return releaseUrl;
}
133 changes: 133 additions & 0 deletions scripts/dataset/publish/gitlab/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import fsApi from 'fs';
import path from 'path';

import config from 'config';
import dotenv from 'dotenv';
import FormData from 'form-data';
import nodeFetch from 'node-fetch';

import GitLab from '../../../../src/reporter/gitlab/index.js';
import * as readme from '../../assets/README.template.js';
import logger from '../../logger/index.js';

dotenv.config();

export default async function publish({
archivePath,
releaseDate,
stats,
}) {
let projectId = null;
const gitlabAPIUrl = config.get('@opentermsarchive/engine.dataset.apiBaseURL');

const [ owner, repo ] = new URL(config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL'))
.pathname
.split('/')
.filter(Boolean);
const commonParams = { owner, repo };

try {
const repositoryPath = `${commonParams.owner}/${commonParams.repo}`;

const options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);

options.method = 'GET';
options.headers = {
'Content-Type': 'application/json',
...options.headers,
};

const response = await nodeFetch(
`${gitlabAPIUrl}/projects/${encodeURIComponent(repositoryPath)}`,
options,
);
const res = await response.json();

projectId = res.id;
} catch (error) {
logger.error(`Error while obtaining projectId: ${error}`);
projectId = null;
}

const tagName = `${path.basename(archivePath, path.extname(archivePath))}`; // use archive filename as Git tag

try {
let options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);

options.method = 'POST';
options.body = {
ref: 'main',
tag_name: tagName,
name: readme.title({ releaseDate }),
description: readme.body(stats),
};
options.headers = {
'Content-Type': 'application/json',
...options.headers,
};

options.body = JSON.stringify(options.body);

const releaseResponse = await nodeFetch(
`${gitlabAPIUrl}/projects/${projectId}/releases`,
options,
);
const releaseRes = await releaseResponse.json();

const releaseId = releaseRes.commit.id;

logger.info(`Created release with releaseId: ${releaseId}`);

// Upload the package
options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);
options.method = 'PUT';
options.body = fsApi.createReadStream(archivePath);

// restrict characters to the ones allowed by GitLab APIs
const packageName = config.get('@opentermsarchive/engine.dataset.title').replace(/[^a-zA-Z0-9.\-_]/g, '-');
const packageVersion = tagName.replace(/[^a-zA-Z0-9.\-_]/g, '-');
const packageFileName = archivePath.replace(/[^a-zA-Z0-9.\-_/]/g, '-');

logger.debug(`packageName: ${packageName}, packageVersion: ${packageVersion} packageFileName: ${packageFileName}`);

const packageResponse = await nodeFetch(
`${gitlabAPIUrl}/projects/${projectId}/packages/generic/${packageName}/${packageVersion}/${packageFileName}?status=default&select=package_file`,
options,
);
const packageRes = await packageResponse.json();

const packageFilesId = packageRes.id;

logger.debug(`package file id: ${packageFilesId}`);

// use the package id to build the download url for the release
const publishedPackageUrl = `${config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL')}/-/package_files/${packageFilesId}/download`;

// Create the release and link the package
const formData = new FormData();

formData.append('name', archivePath);
formData.append('url', publishedPackageUrl);
formData.append('file', fsApi.createReadStream(archivePath), { filename: path.basename(archivePath) });

options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);
options.method = 'POST';
options.headers = {
...formData.getHeaders(),
...options.headers,
};
options.body = formData;

const uploadResponse = await nodeFetch(
`${gitlabAPIUrl}/projects/${projectId}/releases/${tagName}/assets/links`,
options,
);
const uploadRes = await uploadResponse.json();
const releaseUrl = uploadRes.direct_asset_url;

return releaseUrl;
} catch (error) {
logger.error('Failed to create release or upload ZIP file:', error);
throw error;
}
}
43 changes: 11 additions & 32 deletions scripts/dataset/publish/index.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,15 @@
import fsApi from 'fs';
import path from 'path';
import url from 'url';
import publishGitHub from './github/index.js';
import publishGitLab from './gitlab/index.js';

import config from 'config';
import { Octokit } from 'octokit';
export default function publishRelease({ archivePath, releaseDate, stats }) {
// If both GitHub and GitLab tokens are defined, GitHub takes precedence
if (process.env.OTA_ENGINE_GITHUB_TOKEN) {
return publishGitHub({ archivePath, releaseDate, stats });
}
MattiSG marked this conversation as resolved.
Show resolved Hide resolved

import * as readme from '../assets/README.template.js';
if (process.env.OTA_ENGINE_GITLAB_TOKEN) {
return publishGitLab({ archivePath, releaseDate, stats });
}

export default async function publish({ archivePath, releaseDate, stats }) {
const octokit = new Octokit({ auth: process.env.OTA_ENGINE_GITHUB_TOKEN });

const [ owner, repo ] = url.parse(config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL')).pathname.split('/').filter(component => component);

const tagName = `${path.basename(archivePath, path.extname(archivePath))}`; // use archive filename as Git tag

const { data: { upload_url: uploadUrl, html_url: releaseUrl } } = await octokit.rest.repos.createRelease({
owner,
repo,
tag_name: tagName,
name: readme.title({ releaseDate }),
body: readme.body(stats),
});

await octokit.rest.repos.uploadReleaseAsset({
data: fsApi.readFileSync(archivePath),
headers: {
'content-type': 'application/zip',
'content-length': fsApi.statSync(archivePath).size,
},
name: path.basename(archivePath),
url: uploadUrl,
});

return releaseUrl;
throw new Error('No GitHub or GitLab token found in environment variables (OTA_ENGINE_GITHUB_TOKEN or OTA_ENGINE_GITLAB_TOKEN). Cannot publish the dataset without authentication.');
}
Ndpnt marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 9 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,17 @@ export default async function track({ services, types, extractOnly, schedule })
logger.warn('Environment variable "OTA_ENGINE_SENDINBLUE_API_KEY" was not found; the Notifier module will be ignored');
}

if (process.env.OTA_ENGINE_GITHUB_TOKEN) {
if (config.has('@opentermsarchive/engine.reporter.githubIssues.repositories.declarations')) {
try {
const reporter = new Reporter(config.get('@opentermsarchive/engine.reporter'));

await reporter.initialize();
archivist.attach(reporter);
} catch (error) {
logger.error('Cannot instantiate the Reporter module; it will be ignored:', error);
}
} else {
logger.warn('Configuration key "reporter.githubIssues.repositories.declarations" was not found; issues on the declarations repository cannot be created');
if (process.env.OTA_ENGINE_GITHUB_TOKEN || process.env.OTA_ENGINE_GITLAB_TOKEN) {
try {
const reporter = new Reporter(config.get('@opentermsarchive/engine.reporter'));

await reporter.initialize();
archivist.attach(reporter);
} catch (error) {
logger.error('Cannot instantiate the Reporter module; it will be ignored:', error);
}
} else {
logger.warn('Environment variable "OTA_ENGINE_GITHUB_TOKEN" was not found; the Reporter module will be ignored');
logger.warn('Environment variable with token for GitHub or GitLab was not found; the Reporter module will be ignored');
}

if (!schedule) {
Expand Down
13 changes: 13 additions & 0 deletions src/reporter/factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import GitHub from './github/index.js';
import GitLab from './gitlab/index.js';

export function createReporter(config) {
switch (config.type) {
case 'github':
return new GitHub(config.repositories.declarations);
case 'gitlab':
return new GitLab(config.repositories.declarations, config.baseURL, config.apiBaseURL);
default:
throw new Error(`Unsupported reporter type: ${config.type}`);
}
}
16 changes: 14 additions & 2 deletions src/reporter/github.js → src/reporter/github/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createRequire } from 'module';

import { Octokit } from 'octokit';

import logger from '../logger/index.js';
import logger from '../../logger/index.js';

const require = createRequire(import.meta.url);

Expand All @@ -14,7 +14,7 @@ export default class GitHub {
static ISSUE_STATE_ALL = 'all';

constructor(repository) {
const { version } = require('../../package.json');
const { version } = require('../../../package.json');

this.octokit = new Octokit({
auth: process.env.OTA_ENGINE_GITHUB_TOKEN,
Expand Down Expand Up @@ -198,4 +198,16 @@ export default class GitHub {
logger.error(`Failed to update issue "${title}": ${error.stack}`);
}
}

generateDeclarationURL(serviceName) {
return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/declarations/${encodeURIComponent(serviceName)}.json`;
}

generateVersionURL(serviceName, termsType) {
return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
}

generateSnapshotsBaseUrl(serviceName, termsType) {
return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createRequire } from 'module';
import { expect } from 'chai';
import nock from 'nock';

import GitHub from './github.js';
import GitHub from './index.js';

const require = createRequire(import.meta.url);

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createRequire } from 'module';

import chai from 'chai';

import { MANAGED_BY_OTA_MARKER } from './github.js';
import { MANAGED_BY_OTA_MARKER } from './index.js';

const require = createRequire(import.meta.url);

Expand Down
Loading
Loading