From 5eb6985ffdf0ae70396eb63946c22a65c4a378ef Mon Sep 17 00:00:00 2001 From: davide-pi Date: Wed, 11 Sep 2024 22:41:25 +0200 Subject: [PATCH] Add snapcraft license. Update snapcraft version to inherit from snapcraft-base class. Add snapcraft base url in configurations. --- config/custom-environment-variables.yml | 2 + config/default.yml | 2 + core/server/server.js | 3 ++ doc/server-secrets.md | 6 +++ services/snapcraft/snapcraft-base.js | 24 +++++++++++ .../snapcraft/snapcraft-licence.service.js | 41 +++++++++++++++++++ services/snapcraft/snapcraft-licence.spec.js | 14 +++++++ .../snapcraft/snapcraft-licence.tester.js | 14 +++++++ .../snapcraft/snapcraft-version.service.js | 36 ++++++++-------- 9 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 services/snapcraft/snapcraft-base.js create mode 100644 services/snapcraft/snapcraft-licence.service.js create mode 100644 services/snapcraft/snapcraft-licence.spec.js create mode 100644 services/snapcraft/snapcraft-licence.tester.js diff --git a/config/custom-environment-variables.yml b/config/custom-environment-variables.yml index 6686b245c8faa..f3d923fb70f4a 100644 --- a/config/custom-environment-variables.yml +++ b/config/custom-environment-variables.yml @@ -56,6 +56,8 @@ public: authorizedOrigins: 'OBS_ORIGINS' pypi: baseUri: 'PYPI_URL' + snapcraft: + baseUri: 'SNAPCRAFT_URL' sonar: authorizedOrigins: 'SONAR_ORIGINS' teamcity: diff --git a/config/default.yml b/config/default.yml index cea2edad7b60d..e8cfb01240fcb 100644 --- a/config/default.yml +++ b/config/default.yml @@ -24,6 +24,8 @@ public: authorizedOrigins: 'https://api.opensuse.org' pypi: baseUri: 'https://pypi.org' + snapcraft: + baseUri: 'https://api.snapcraft.io/v2/snaps/info' weblate: authorizedOrigins: 'https://hosted.weblate.org' trace: false diff --git a/core/server/server.js b/core/server/server.js index f5621d9411257..83686c9e390d5 100644 --- a/core/server/server.js +++ b/core/server/server.js @@ -142,6 +142,9 @@ const publicConfigSchema = Joi.object({ pypi: { baseUri: requiredUrl, }, + snapcraft: { + baseUri: requiredUrl, + }, sonar: defaultService, teamcity: defaultService, weblate: defaultService, diff --git a/doc/server-secrets.md b/doc/server-secrets.md index d7d66fc3cdaca..ca2b154c4b420 100644 --- a/doc/server-secrets.md +++ b/doc/server-secrets.md @@ -290,6 +290,12 @@ Create an account, sign in and obtain generate a key on your `PYPI_URL` can be used to optionally send all the PyPI requests to a Self-hosted Pypi registry, users can also override this by query parameter `pypiBaseUrl`. +### Snapcraft + +- `SNAPCRAFT_URL` (yml: `public.snapcraft.baseUri`) + +`SNAPCRAFT_URL` can be used to optionally send all the Snapcraft requests to a Self-hosted Snapcraft registry. + ### SymfonyInsight (formerly Sensiolabs) - `SL_INSIGHT_USER_UUID` (yml: `private.sl_insight_userUuid`) diff --git a/services/snapcraft/snapcraft-base.js b/services/snapcraft/snapcraft-base.js new file mode 100644 index 0000000000000..59288236ec5b7 --- /dev/null +++ b/services/snapcraft/snapcraft-base.js @@ -0,0 +1,24 @@ +import config from 'config' +import { BaseJsonService, pathParam } from '../index.js' + +export const snapcraftPackageParam = pathParam({ + name: 'package', + example: 'Redis', +}) + +export const snapcraftBaseParams = [snapcraftPackageParam] + +export default class SnapcraftBase extends BaseJsonService { + async fetch(schema, { packageName }) { + const snapcraftBaseUrl = + config.util.toObject().public.services.snapcraft.baseUri + return await this._requestJson({ + schema, + url: `${snapcraftBaseUrl}/${packageName}`, + options: { + headers: { 'Snap-Device-Series': 16 }, + }, + httpErrors: { 404: 'package not found' }, + }) + } +} diff --git a/services/snapcraft/snapcraft-licence.service.js b/services/snapcraft/snapcraft-licence.service.js new file mode 100644 index 0000000000000..3be42f1a92260 --- /dev/null +++ b/services/snapcraft/snapcraft-licence.service.js @@ -0,0 +1,41 @@ +import Joi from 'joi' +import { renderLicenseBadge } from '../licenses.js' +import SnapcraftBase, { snapcraftPackageParam } from './snapcraft-base.js' + +const licenseSchema = Joi.object({ + snap: Joi.object({ + license: Joi.string().required(), + }).required(), +}).required() + +export default class SnapcraftLicense extends SnapcraftBase { + static category = 'license' + + static route = { + base: 'snapcraft/l', + pattern: ':package', + } + + static openApi = { + '/snapcraft/l/{package}': { + get: { + summary: 'Snapcraft License', + parameters: [snapcraftPackageParam], + }, + }, + } + + static render({ license }) { + return renderLicenseBadge({ license }) + } + + static transform(apiData) { + return apiData.snap.license + } + + async handle({ package: packageName }) { + const parsedData = await this.fetch(licenseSchema, { packageName }) + const license = this.constructor.transform(parsedData) + return this.constructor.render({ license }) + } +} diff --git a/services/snapcraft/snapcraft-licence.spec.js b/services/snapcraft/snapcraft-licence.spec.js new file mode 100644 index 0000000000000..eed6c2030d1ae --- /dev/null +++ b/services/snapcraft/snapcraft-licence.spec.js @@ -0,0 +1,14 @@ +import { test, given } from 'sazerac' +import SnapcraftLicense from './snapcraft-licence.service.js' + +describe('SnapcraftLicense', function () { + const testApiData = { + snap: { + license: 'BSD-3-Clause', + }, + } + + test(SnapcraftLicense.prototype.transform, () => { + given(testApiData).expect('BSD-3-Clause') + }) +}) diff --git a/services/snapcraft/snapcraft-licence.tester.js b/services/snapcraft/snapcraft-licence.tester.js new file mode 100644 index 0000000000000..a2217ba5955ab --- /dev/null +++ b/services/snapcraft/snapcraft-licence.tester.js @@ -0,0 +1,14 @@ +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('Snapcraft license (valid)').get('/redis.json').expectBadge({ + label: 'license', + message: 'BSD-3-Clause', +}) + +t.create('Snapcraft license(invalid)') + .get('/this_package_doesnt_exist.json') + .expectBadge({ + label: 'license', + message: 'package not found', + }) diff --git a/services/snapcraft/snapcraft-version.service.js b/services/snapcraft/snapcraft-version.service.js index 1d18f60449bcb..da97caf98469e 100644 --- a/services/snapcraft/snapcraft-version.service.js +++ b/services/snapcraft/snapcraft-version.service.js @@ -1,6 +1,7 @@ import Joi from 'joi' -import { BaseJsonService, NotFound, pathParams, queryParam } from '../index.js' +import { NotFound, pathParams, queryParam } from '../index.js' import { renderVersionBadge } from '../version.js' +import SnapcraftBase, { snapcraftPackageParam } from './snapcraft-base.js' const queryParamSchema = Joi.object({ arch: Joi.string(), @@ -22,7 +23,7 @@ const versionSchema = Joi.object({ .required(), }).required() -export default class SnapcraftVersion extends BaseJsonService { +export default class SnapcraftVersion extends SnapcraftBase { static category = 'version' static route = { @@ -36,10 +37,10 @@ export default class SnapcraftVersion extends BaseJsonService { static openApi = { '/snapcraft/v/{package}/{track}/{risk}': { get: { - summary: 'Snapcraft version', + summary: 'Snapcraft Version', parameters: [ + snapcraftPackageParam, ...pathParams( - { name: 'package', example: 'chromium' }, { name: 'track', example: 'latest' }, { name: 'risk', example: 'stable' }, ), @@ -54,7 +55,11 @@ export default class SnapcraftVersion extends BaseJsonService { }, } - transform(apiData, track, risk, arch) { + static render({ version }) { + return renderVersionBadge({ version }) + } + + static transform(apiData, track, risk, arch) { const channelMap = apiData['channel-map'] let filteredChannelMap = channelMap.filter( ({ channel }) => channel.architecture === arch, @@ -79,20 +84,15 @@ export default class SnapcraftVersion extends BaseJsonService { } async handle({ package: packageName, track, risk }, { arch = 'amd64' }) { - const parsedData = await this._requestJson({ - schema: versionSchema, - options: { - headers: { 'Snap-Device-Series': 16 }, - }, - url: `https://api.snapcraft.io/v2/snaps/info/${packageName}`, - httpErrors: { - 404: 'package not found', - }, - }) + const parsedData = await this.fetch(versionSchema, { packageName }) // filter results by track, risk and arch - const { version } = this.transform(parsedData, track, risk, arch) - - return renderVersionBadge({ version }) + const { version } = this.constructor.transform( + parsedData, + track, + risk, + arch, + ) + return this.constructor.render({ version }) } }